使用 SQL 服务器,如何根据分隔字符串作为条件查询 table?
With SQL Server, How can I query a table based on a delimited string as the criteria?
我有以下表格:
tbl_File:
FileID | Filename
-----------------
1 | test.jpg
和
tbl_Tag:
TagID | TagName
---------------
1 | Red
和
tbl_Tag文件:
ID | TagID | FileID
-------------------
1 | 1 | 1
我需要针对这些表传递非包含查询。例如,想象一下 select 一个或多个标签的复选框列表,然后是一个搜索按钮。我需要将 TagID 作为 PIPE 分隔字符串传递给查询,例如“1|2|5|”
搜索结果需要不包含,比如必须满足所有条件。如果 select 编辑了 3 个标签,则结果将是所有 3 个标签都与之关联的文件。
我想我把它弄得太复杂了,但尝试使用 charindex 和其他东西遍历标签来处理字符串,但似乎必须有更简单的方法。
我想把它作为一个函数...比如
SELECT FileID, Filename
FROM tbl_Files
WHERE dbo.udf_FileExistswithTags(@Tags, FileID) = 1
有什么有效的方法吗?
从您的示例场景来看,实际的 "need" 并不是要传递竖线分隔的字符串。我强烈建议放弃该想法并在存储过程中使用 Table 值参数。这有很多优点,因为您不会达到数据类型限制或 "number of parameters" 限制,而这些限制可能会出现在非常大的条件集上。此外,它不再需要 运行 一个(可能非常慢的)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;
}
无论你 运行 你的 proc 来自哪里(这里是粗略的代码):
var records = BuildFileNameTokenRecords(listofstrings);
var sqlCmd = sqlDb.GetStoredProcCommand("FileExists");
sqlDb.AddInParameter(sqlCmd, "tvpFilenameTokens", SqlDbType.Structured, records);
ExecuteNonQuery(sqlCmd);
过滤您的 select 语句然后简单地变成加入 table 参数中的标记的问题。像这样:
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 的字符串。有关更多信息,请阅读:http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K](
@pString VARCHAR(8000), --WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
@pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS (--10E+1 or 10 rows
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString, t.N, 1) = @pDelimiter
),
cteLen(N1, L1) AS(--==== Return start and length (for use in substring)
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter, @pString, s.N1), 0) - s.N1, 8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
您的查询现在是:
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)
这是一个不需要 UDF 的选项。
可以说这也很复杂
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)
在数据类型和索引等方面存在一些复杂性,但您可以看到这个概念 - 您只会获得与您传入的字符串匹配的记录。
子表 LimitedTagTable
是您的标签列表,由您的输入竖线分隔字符串过滤
子表FinalSet
将您的有限标签列表加入您的文件列表
列 TotalMatches
计算出您的文件有多少标签匹配
最后,这一行将输出限制为那些具有足够匹配项的文件:
FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)
请尝试不同的输入和数据集,看看它是否适合我做出的一些假设。
我正在回答我自己的问题,希望有人能让我知道 if/how 它有缺陷。到目前为止它似乎在工作,但只是早期测试。
函数:
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
然后在查询中:
SELECT * 从 tbl_File a WHERE dbo.udf_FileExistsByTags(a.FileID, @Tags) = 1
所以现在我正在寻找错误。
你怎么看?可能不是每一个都有效,但是这种搜索只会定期使用。
这是一个应该缩放的选项。 SQL Server 2005 可以使用所有功能。它使用 CTE 来分隔查询部分,该部分仅查找 FileID
具有 all 的部分传入的 TagID
的列表,然后将 FileID
的列表加入 [File]
table 以获取详细信息。它还使用 INNER JOIN
而不是 IN
列表来匹配 TagID。
请注意,下面的示例使用了 SQLCLR 拆分器,它在 SQL# 库中免费提供(我编写的,但此功能在免费版本中)。使用的具体分离器不是重要部分;它应该只是 SQLCLR,一个内联计数-table(就像@wewesthemenace 的回答中使用的那个),或者是 XML 方法。只是不要使用基于 WHILE 循环或递归 CTE 的拆分器。
---- TEST SETUP
DECLARE @File TABLE
(
FileID INT NOT NULL PRIMARY KEY,
[Filename] NVARCHAR(200) NOT NULL
);
DECLARE @TagFile TABLE
(
TagID INT NOT NULL,
FileID INT NOT NULL,
PRIMARY KEY (TagID, FileID)
);
INSERT INTO @File VALUES (1, 'File1.txt');
INSERT INTO @File VALUES (2, 'File2.txt');
INSERT INTO @File VALUES (3, 'File3.txt');
INSERT INTO @TagFile VALUES (1, 1);
INSERT INTO @TagFile VALUES (2, 1);
INSERT INTO @TagFile VALUES (5, 1);
INSERT INTO @TagFile VALUES (1, 2);
INSERT INTO @TagFile VALUES (2, 2);
INSERT INTO @TagFile VALUES (4, 2);
INSERT INTO @TagFile VALUES (1, 3);
INSERT INTO @TagFile VALUES (2, 3);
INSERT INTO @TagFile VALUES (5, 3);
INSERT INTO @TagFile VALUES (6, 3);
---- DONE WITH TEST SETUP
DECLARE @TagsToGet VARCHAR(100); -- this would be the proc input parameter
SET @TagsToGet = '1|2|5';
CREATE TABLE #Tags (TagID INT NOT NULL PRIMARY KEY);
DECLARE @NumTags INT;
INSERT INTO #Tags (TagID)
SELECT split.SplitVal
FROM SQL#.String_Split4k(@TagsToGet, '|', 1) split;
SET @NumTags = @@ROWCOUNT;
;WITH files AS
(
SELECT tf.FileID
FROM @TagFile tf
INNER JOIN #Tags tg
ON tg.TagID = tf.TagID
GROUP BY tf.FileID
HAVING COUNT(*) = @NumTags
)
SELECT fl.*
FROM @File fl
INNER JOIN files
ON files.FileID = fl.FileID
ORDER BY fl.[Filename] ASC;
DROP TABLE #Tags; -- don't need this if code above is placed in a proc
结果:
FileID Filename
1 File1.txt
3 File3.txt
备注
尽管我很喜欢 TVP(而且我确实喜欢,当它们正确完成并适当使用时),我想说它们对于这种小规模、一维数组来说有点过分了设想。与使用 SQLCLR 流式 TVF 字符串拆分器相比,实际上不会有任何性能提升,但它需要更多应用程序代码和额外的用户定义 Table 类型,必须先进行更新删除所有引用它的过程。这种情况不会一直发生,但需要考虑长期维护成本。
TagFile
和从拆分操作填充的临时 table 之间的 JOIN 应该比使用带有子查询的 IN
列表更有效拆分操作。 IN
列表是其中所有值作为它们自己的 OR 条件的简写形式。因此 JOIN 是一种完全基于集合的方法,它让查询优化器完成它的工作。
我用来测试的结构@TagFile
table里面只有两个相关的ID:TagID
和FileID
。它没有我认为是此 table 上的 IDENTITY 字段的 ID
字段。除非有非常具体的原因需要该 IDENTITY 字段,否则我建议将其删除。它增加了固有的好处,因为 TagID
和 FileID
的组合是 一个自然键(即它既不是 NULL 又是唯一的)。如果 table 的 Clustered PK 只是这两个字段,那么即使 TagFile
中有数百万行,连接到这些拆分 TagID 的临时 table 也会非常快.
这种方法比尝试通过每个 FileID
的函数处理这个方法效果更好的一个原因是(除了明显的基于集合比基于游标的原因之外)是TagID
s 的列表对于所有要检查的文件都是相同的。所以把它分开不止一次是浪费精力。
通过不在查询中内联拆分 TagID 列表,我能够捕获该列表中的元素数量而无需额外的努力。因此,这样就无需进行二次计算。
我有以下表格:
tbl_File:
FileID | Filename
-----------------
1 | test.jpg
和
tbl_Tag:
TagID | TagName
---------------
1 | Red
和
tbl_Tag文件:
ID | TagID | FileID
-------------------
1 | 1 | 1
我需要针对这些表传递非包含查询。例如,想象一下 select 一个或多个标签的复选框列表,然后是一个搜索按钮。我需要将 TagID 作为 PIPE 分隔字符串传递给查询,例如“1|2|5|”
搜索结果需要不包含,比如必须满足所有条件。如果 select 编辑了 3 个标签,则结果将是所有 3 个标签都与之关联的文件。
我想我把它弄得太复杂了,但尝试使用 charindex 和其他东西遍历标签来处理字符串,但似乎必须有更简单的方法。
我想把它作为一个函数...比如
SELECT FileID, Filename
FROM tbl_Files
WHERE dbo.udf_FileExistswithTags(@Tags, FileID) = 1
有什么有效的方法吗?
从您的示例场景来看,实际的 "need" 并不是要传递竖线分隔的字符串。我强烈建议放弃该想法并在存储过程中使用 Table 值参数。这有很多优点,因为您不会达到数据类型限制或 "number of parameters" 限制,而这些限制可能会出现在非常大的条件集上。此外,它不再需要 运行 一个(可能非常慢的)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;
}
无论你 运行 你的 proc 来自哪里(这里是粗略的代码):
var records = BuildFileNameTokenRecords(listofstrings);
var sqlCmd = sqlDb.GetStoredProcCommand("FileExists");
sqlDb.AddInParameter(sqlCmd, "tvpFilenameTokens", SqlDbType.Structured, records);
ExecuteNonQuery(sqlCmd);
过滤您的 select 语句然后简单地变成加入 table 参数中的标记的问题。像这样:
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 的字符串。有关更多信息,请阅读:http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K](
@pString VARCHAR(8000), --WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
@pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS (--10E+1 or 10 rows
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString, t.N, 1) = @pDelimiter
),
cteLen(N1, L1) AS(--==== Return start and length (for use in substring)
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter, @pString, s.N1), 0) - s.N1, 8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
您的查询现在是:
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)
这是一个不需要 UDF 的选项。
可以说这也很复杂
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)
在数据类型和索引等方面存在一些复杂性,但您可以看到这个概念 - 您只会获得与您传入的字符串匹配的记录。
子表 LimitedTagTable
是您的标签列表,由您的输入竖线分隔字符串过滤
子表FinalSet
将您的有限标签列表加入您的文件列表
列 TotalMatches
计算出您的文件有多少标签匹配
最后,这一行将输出限制为那些具有足够匹配项的文件:
FinalSet.TotalMatches = (LEN(@TagList) - LEN(REPLACE(@TagList,'|','')) + 1)
请尝试不同的输入和数据集,看看它是否适合我做出的一些假设。
我正在回答我自己的问题,希望有人能让我知道 if/how 它有缺陷。到目前为止它似乎在工作,但只是早期测试。
函数:
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
然后在查询中:
SELECT * 从 tbl_File a WHERE dbo.udf_FileExistsByTags(a.FileID, @Tags) = 1
所以现在我正在寻找错误。
你怎么看?可能不是每一个都有效,但是这种搜索只会定期使用。
这是一个应该缩放的选项。 SQL Server 2005 可以使用所有功能。它使用 CTE 来分隔查询部分,该部分仅查找 FileID
具有 all 的部分传入的 TagID
的列表,然后将 FileID
的列表加入 [File]
table 以获取详细信息。它还使用 INNER JOIN
而不是 IN
列表来匹配 TagID。
请注意,下面的示例使用了 SQLCLR 拆分器,它在 SQL# 库中免费提供(我编写的,但此功能在免费版本中)。使用的具体分离器不是重要部分;它应该只是 SQLCLR,一个内联计数-table(就像@wewesthemenace 的回答中使用的那个),或者是 XML 方法。只是不要使用基于 WHILE 循环或递归 CTE 的拆分器。
---- TEST SETUP
DECLARE @File TABLE
(
FileID INT NOT NULL PRIMARY KEY,
[Filename] NVARCHAR(200) NOT NULL
);
DECLARE @TagFile TABLE
(
TagID INT NOT NULL,
FileID INT NOT NULL,
PRIMARY KEY (TagID, FileID)
);
INSERT INTO @File VALUES (1, 'File1.txt');
INSERT INTO @File VALUES (2, 'File2.txt');
INSERT INTO @File VALUES (3, 'File3.txt');
INSERT INTO @TagFile VALUES (1, 1);
INSERT INTO @TagFile VALUES (2, 1);
INSERT INTO @TagFile VALUES (5, 1);
INSERT INTO @TagFile VALUES (1, 2);
INSERT INTO @TagFile VALUES (2, 2);
INSERT INTO @TagFile VALUES (4, 2);
INSERT INTO @TagFile VALUES (1, 3);
INSERT INTO @TagFile VALUES (2, 3);
INSERT INTO @TagFile VALUES (5, 3);
INSERT INTO @TagFile VALUES (6, 3);
---- DONE WITH TEST SETUP
DECLARE @TagsToGet VARCHAR(100); -- this would be the proc input parameter
SET @TagsToGet = '1|2|5';
CREATE TABLE #Tags (TagID INT NOT NULL PRIMARY KEY);
DECLARE @NumTags INT;
INSERT INTO #Tags (TagID)
SELECT split.SplitVal
FROM SQL#.String_Split4k(@TagsToGet, '|', 1) split;
SET @NumTags = @@ROWCOUNT;
;WITH files AS
(
SELECT tf.FileID
FROM @TagFile tf
INNER JOIN #Tags tg
ON tg.TagID = tf.TagID
GROUP BY tf.FileID
HAVING COUNT(*) = @NumTags
)
SELECT fl.*
FROM @File fl
INNER JOIN files
ON files.FileID = fl.FileID
ORDER BY fl.[Filename] ASC;
DROP TABLE #Tags; -- don't need this if code above is placed in a proc
结果:
FileID Filename
1 File1.txt
3 File3.txt
备注
尽管我很喜欢 TVP(而且我确实喜欢,当它们正确完成并适当使用时),我想说它们对于这种小规模、一维数组来说有点过分了设想。与使用 SQLCLR 流式 TVF 字符串拆分器相比,实际上不会有任何性能提升,但它需要更多应用程序代码和额外的用户定义 Table 类型,必须先进行更新删除所有引用它的过程。这种情况不会一直发生,但需要考虑长期维护成本。
TagFile
和从拆分操作填充的临时 table 之间的 JOIN 应该比使用带有子查询的IN
列表更有效拆分操作。IN
列表是其中所有值作为它们自己的 OR 条件的简写形式。因此 JOIN 是一种完全基于集合的方法,它让查询优化器完成它的工作。我用来测试的结构
@TagFile
table里面只有两个相关的ID:TagID
和FileID
。它没有我认为是此 table 上的 IDENTITY 字段的ID
字段。除非有非常具体的原因需要该 IDENTITY 字段,否则我建议将其删除。它增加了固有的好处,因为TagID
和FileID
的组合是 一个自然键(即它既不是 NULL 又是唯一的)。如果 table 的 Clustered PK 只是这两个字段,那么即使TagFile
中有数百万行,连接到这些拆分 TagID 的临时 table 也会非常快.这种方法比尝试通过每个
FileID
的函数处理这个方法效果更好的一个原因是(除了明显的基于集合比基于游标的原因之外)是TagID
s 的列表对于所有要检查的文件都是相同的。所以把它分开不止一次是浪费精力。通过不在查询中内联拆分 TagID 列表,我能够捕获该列表中的元素数量而无需额外的努力。因此,这样就无需进行二次计算。