Sql server 使用SQL Server,如何根据分隔字符串作为条件查询表?

Sql server 使用SQL Server,如何根据分隔字符串作为条件查询表?,sql-server,tsql,Sql Server,Tsql,我有以下表格: tbl_文件: FileID | Filename ----------------- 1 | test.jpg TagID | TagName --------------- 1 | Red ID | TagID | FileID ------------------- 1 | 1 | 1 及 待标签: FileID | Filename ----------------- 1 | test.jpg TagID | TagName

我有以下表格:

tbl_文件

FileID | Filename
-----------------
1      | test.jpg
TagID | TagName
---------------
1     | Red
ID | TagID | FileID
-------------------
1  | 1     | 1

待标签

FileID | Filename
-----------------
1      | test.jpg
TagID | TagName
---------------
1     | Red
ID | TagID | FileID
-------------------
1  | 1     | 1

待归档文件

FileID | Filename
-----------------
1      | test.jpg
TagID | TagName
---------------
1     | Red
ID | TagID | FileID
-------------------
1  | 1     | 1
我需要对这些表传递一个非包含性查询。例如,设想一个复选框列表来选择一个或多个标记,然后是一个搜索按钮。我需要将TagID作为管道分隔字符串传递给查询,例如“1 | 2 | 5 |”

搜索结果必须是非包容性的,例如它是否必须满足所有条件。如果选择了3个标记,则结果将是所有3个标记都与之关联的文件

我认为这太复杂了,但是我试着使用charindex和其他东西来迭代标记以遍历字符串,但似乎有一种更简单的方法

我想把它作为一个函数。。。比如

SELECT FileID, Filename 
FROM tbl_Files 
WHERE dbo.udf_FileExistswithTags(@Tags, FileID) = 1

有什么有效的方法可以做到这一点吗?

从您的示例场景听起来,实际的“需要”不是传递管道分隔的字符串。我强烈建议放弃这个想法,在存储过程中使用表值参数。这有许多优点,因为您不会遇到数据类型限制或“参数数量”限制,而这些限制可能会出现在非常大的标准集上。此外,它不需要运行(可能非常慢的)UDF

在应用程序端将字符串拆分为令牌,然后将每个令牌作为一行插入TVP。示例如下:

在数据库中创建TVP类型:

CREATE TYPE [dbo].[FileNameType] AS TABLE
(
    fileName varchar(1000)
)
在应用程序端,将文件名标记列表构建到记录集中:

private static List<SqlDataRecord> BuildFileNameTokenRecords(IEnumerable<string> tokens)
        {
            var records = new List<SqlDataRecord>();
            foreach (string token in tokens){
var record = new SqlDataRecord(
                    new SqlMetaData[]
                    {
                        new SqlMetaData("fileName", SqlDbType.Varchar),
                    }
                );
                records.Add(record);
            }
            return records;
        }
过滤select语句就变成了在表参数中加入标记的问题。大概是这样的:

CREATE PROCEDURE dbo.FileExists
    (
     -- Put additional parameters here
     @tvpFilenameTokens dbo.FileNameType READONLY,
    )
    AS
BEGIN
    SELECT FileID, Filename 
    FROM tbl_Files INNER JOIN @tvpFilenameTokens
    ON tbl_Files.FileID = @tvpFilenameTokens.fileName 
END

从您的示例场景听起来,实际的“需要”不是传递管道分隔的字符串。我强烈建议放弃这个想法,在存储过程中使用表值参数。这有许多优点,因为您不会遇到数据类型限制或“参数数量”限制,而这些限制可能会出现在非常大的标准集上。此外,它不需要运行(可能非常慢的)UDF

在应用程序端将字符串拆分为令牌,然后将每个令牌作为一行插入TVP。示例如下:

在数据库中创建TVP类型:

CREATE TYPE [dbo].[FileNameType] AS TABLE
(
    fileName varchar(1000)
)
在应用程序端,将文件名标记列表构建到记录集中:

private static List<SqlDataRecord> BuildFileNameTokenRecords(IEnumerable<string> tokens)
        {
            var records = new List<SqlDataRecord>();
            foreach (string token in tokens){
var record = new SqlDataRecord(
                    new SqlMetaData[]
                    {
                        new SqlMetaData("fileName", SqlDbType.Varchar),
                    }
                );
                records.Add(record);
            }
            return records;
        }
过滤select语句就变成了在表参数中加入标记的问题。大概是这样的:

CREATE PROCEDURE dbo.FileExists
    (
     -- Put additional parameters here
     @tvpFilenameTokens dbo.FileNameType READONLY,
    )
    AS
BEGIN
    SELECT FileID, Filename 
    FROM tbl_Files INNER JOIN @tvpFilenameTokens
    ON tbl_Files.FileID = @tvpFilenameTokens.fileName 
END

下面是Jeff Moden编写的名为DelimitedSplit8K的函数。这用于拆分长度不超过8000的字符串。有关更多信息,请阅读以下内容:

您的查询现在是:

DECLARE @pString VARCHAR(8000) = '1|3|5'

SELECT
    f.*
FROM tbl_File f
INNER JOIN tbl_TagFile tf ON tf.FileID = f.FileID
WHERE
    tf.TagID IN(SELECT CAST(item AS INT) FROM dbo.DelimitedSplit8K(@pString, '|'))
GROUP BY f.FileID, f.FileName
HAVING COUNT(tf.ID) = (LEN(@pString) - LEN(REPLACE(@pString,'|','')) + 1)
下面的语句通过计算分隔符
|
+1的出现次数来计算参数中
TagID
的数量

(LEN(@pString) - LEN(REPLACE(@pString,'|','')) + 1)

下面是Jeff Moden编写的名为DelimitedSplit8K的函数。这用于拆分长度不超过8000的字符串。有关更多信息,请阅读以下内容:

您的查询现在是:

DECLARE @pString VARCHAR(8000) = '1|3|5'

SELECT
    f.*
FROM tbl_File f
INNER JOIN tbl_TagFile tf ON tf.FileID = f.FileID
WHERE
    tf.TagID IN(SELECT CAST(item AS INT) FROM dbo.DelimitedSplit8K(@pString, '|'))
GROUP BY f.FileID, f.FileName
HAVING COUNT(tf.ID) = (LEN(@pString) - LEN(REPLACE(@pString,'|','')) + 1)
下面的语句通过计算分隔符
|
+1的出现次数来计算参数中
TagID
的数量

(LEN(@pString) - LEN(REPLACE(@pString,'|','')) + 1)

这里有一个不需要自定义项的选项

可以说,这也很复杂

DECLARE @TagList VARCHAR(50)

-- pass in this
SET @TagList = '1|3|6'

SELECT
FinalSet.FileID, 
FinalSet.Tag,
FinalSet.TotalMatches
FROM 
(
    SELECT 
    tbl_TagFile.FileID, 
    tbl_TagFile.Tag,
    COUNT(*) OVER(PARTITION BY tbl_TagFile.FileID) TotalMatches
    FROM 
    (
        SELECT 1 FileID, '1' Tag  UNION ALL
        SELECT 1 , '2'  UNION ALL
        SELECT 1 , '3'  UNION ALL
        SELECT 1 , '6'  UNION ALL
        SELECT 2 , '1'  UNION ALL
        SELECT 2 , '3'
    ) tbl_TagFile
    INNER JOIN
    (
        SELECT tbl_Tag.Tag
        FROM 
        (
        SELECT '1' Tag  UNION ALL
        SELECT '2' UNION ALL
        SELECT '3' UNION ALL
        SELECT '4' UNION ALL
        SELECT '5' UNION ALL
        SELECT '6' 
        ) tbl_Tag
        WHERE '|' + @TagList + '|' LIKE '%|' + Tag + '|%'
    ) LimitedTagTable
    ON LimitedTagTable.Tag = tbl_TagFile.Tag
) FinalSet
WHERE   
FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)
在数据类型、索引和其他方面有一些复杂之处,但您可以看到这个概念——您只获得与传入字符串匹配的记录

subtable
LimitedTagTable
是由输入管道分隔字符串过滤的标记列表

子表
FinalSet
将有限的标记列表加入到文件列表中

TotalMatches
计算出与文件匹配的标记数

最后,此行将输出限制为具有足够匹配项的文件:

FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)

请尝试不同的输入和数据集,看看是否适合,因为我做了一些假设。

这里有一个不需要自定义项的选项

可以说,这也很复杂

DECLARE @TagList VARCHAR(50)

-- pass in this
SET @TagList = '1|3|6'

SELECT
FinalSet.FileID, 
FinalSet.Tag,
FinalSet.TotalMatches
FROM 
(
    SELECT 
    tbl_TagFile.FileID, 
    tbl_TagFile.Tag,
    COUNT(*) OVER(PARTITION BY tbl_TagFile.FileID) TotalMatches
    FROM 
    (
        SELECT 1 FileID, '1' Tag  UNION ALL
        SELECT 1 , '2'  UNION ALL
        SELECT 1 , '3'  UNION ALL
        SELECT 1 , '6'  UNION ALL
        SELECT 2 , '1'  UNION ALL
        SELECT 2 , '3'
    ) tbl_TagFile
    INNER JOIN
    (
        SELECT tbl_Tag.Tag
        FROM 
        (
        SELECT '1' Tag  UNION ALL
        SELECT '2' UNION ALL
        SELECT '3' UNION ALL
        SELECT '4' UNION ALL
        SELECT '5' UNION ALL
        SELECT '6' 
        ) tbl_Tag
        WHERE '|' + @TagList + '|' LIKE '%|' + Tag + '|%'
    ) LimitedTagTable
    ON LimitedTagTable.Tag = tbl_TagFile.Tag
) FinalSet
WHERE   
FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)
在数据类型、索引和其他方面有一些复杂之处,但您可以看到这个概念——您只获得与传入字符串匹配的记录

subtable
LimitedTagTable
是由输入管道分隔字符串过滤的标记列表

子表
FinalSet
将有限的标记列表加入到文件列表中

TotalMatches
计算出与文件匹配的标记数

最后,此行将输出限制为具有足够匹配项的文件:

FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)

请尝试不同的输入和数据集,看看它是否适合,因为我已经做了一些假设。

我在回答我自己的问题,希望有人能告诉我它是否有缺陷。到目前为止,它似乎是工作,但只是早期测试

功能:

ALTER FUNCTION [dbo].[udf_FileExistsByTags]
(
     @FileID int
    ,@Tags nvarchar(max)
)
RETURNS bit
AS
BEGIN
    DECLARE @Exists bit = 0
    DECLARE @Count int = 0
    DECLARE @TagTable TABLE ( FileID int, TagID int )
    DECLARE @Tag int

    WHILE len(@Tags) > 0
    BEGIN
        SET @Tag = CAST(LEFT(@Tags, charindex('|', @Tags + '|') -1) as int)
        SET @Count = @Count + 1
        IF EXISTS (SELECT * FROM tbl_FileTag WHERE FileID = @FileID AND TagID = @Tag )
            BEGIN
                INSERT INTO @TagTable ( FileID, TagID ) VALUES ( @FileID, @Tag )
            END
        SET @Tags = STUFF(@Tags, 1, charindex('|', @Tags + '|'), '')
    END

    SET @Exists = CASE WHEN @Count = (SELECT COUNT(*) FROM @TagTable) THEN 1 ELSE 0 END
    RETURN @Exists

END
然后在查询中:

从tbl_文件a中选择*其中dbo.udf_文件存在stsbytags(a.FileID,@Tags)=1

所以现在我在寻找错误


你觉得怎么样?可能不是每一次都有效,但是这个搜索只会定期使用。

我在回答我自己的问题,希望有人能告诉我它是否有缺陷。到目前为止,它似乎是工作,但只是早期测试

功能:

ALTER FUNCTION [dbo].[udf_FileExistsByTags]
(
     @FileID int
    ,@Tags nvarchar(max)
)
RETURNS bit
AS
BEGIN
    DECLARE @Exists bit = 0
    DECLARE @Count int = 0
    DECLARE @TagTable TABLE ( FileID int, TagID int )
    DECLARE @Tag int

    WHILE len(@Tags) > 0
    BEGIN
        SET @Tag = CAST(LEFT(@Tags, charindex('|', @Tags + '|') -1) as int)
        SET @Count = @Count + 1
        IF EXISTS (SELECT * FROM tbl_FileTag WHERE FileID = @FileID AND TagID = @Tag )
            BEGIN
                INSERT INTO @TagTable ( FileID, TagID ) VALUES ( @FileID, @Tag )
            END
        SET @Tags = STUFF(@Tags, 1, charindex('|', @Tags + '|'), '')
    END

    SET @Exists = CASE WHEN @Count = (SELECT COUNT(*) FROM @TagTable) THEN 1 ELSE 0 END
    RETURN @Exists

END
然后在查询中:

从tbl_文件a中选择*其中dbo.udf_文件存在stsbytags(a.FileID,@Tags)=1

所以现在我在寻找错误


你觉得怎么样?可能不是每一次都有效,但是此搜索只会定期使用。

这里有一个可扩展的选项。所有这些功能都可以追溯到SQLServer2005。它使用一个CTE来分隔查询部分,该部分只查找传递了所有
TagID
s的
FileID
s,然后是
FileID列表