Sql server 使用SQL Server,如何根据分隔字符串作为条件查询表?
我有以下表格: tbl_文件: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
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)
在数据类型、索引和其他方面有一些复杂之处,但您可以看到这个概念——您只获得与传入字符串匹配的记录
subtableLimitedTagTable
是由输入管道分隔字符串过滤的标记列表
子表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)
在数据类型、索引和其他方面有一些复杂之处,但您可以看到这个概念——您只获得与传入字符串匹配的记录
subtableLimitedTagTable
是由输入管道分隔字符串过滤的标记列表
子表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列表