对于 SQL 服务器中的逗号分隔值字符串,在 LIKE 或 IN SPLIT_STRING() 上加入是否更有效?
Is it more efficient to join on LIKE or IN SPLIT_STRING() for a string of comma separated values in SQL Server?
我想将一长串以逗号分隔的值合并为一个值,例如,假设 U.S.:
中的所有城镇
像这样构造我的连接是否更有意义:
[TableA].[Town] IN (SELECT VALUE FROM SPLIT_STRING([TableB].[TownList],','))
或
',' + [TableB].[TownList] + ',' LIKE ('%,' + [TableA].[Town] + ',%')
在此示例中,[TownList]
有 3 行,每行约 300 个以逗号分隔的城镇名称。 [Town]
有大约 200,000 行各个城镇名称。
作为一个理论上的例子,我想更好地理解这些在后端做了什么,以及在哪些场景下有意义?
注意:
这是一个理论问题,可以帮助我解决现实世界的问题。
是的,我知道有更好的方法来进行此连接。
是的,我知道它需要测试,我需要知道查询计划。
从 1000 英尺的角度来看,两者之间的理论 取舍是什么?
从理论、抽象的角度来看,您正在连接两个 table,您想知道哪个连接条件优于另一个。在表达水平上,我注意到很少值得努力去分析。 CPU 依赖的东西在 SQL 服务器中似乎没有太大作用。很少看到分析表情表现的帖子。我只能回忆起其中一个大牌,表明 format
功能很昂贵。
相反,最重要的是使用的计划。这不是我们可以回答的问题;它取决于索引、table 大小和其他元数据。
因此,与所有查询一样,要了解正在发生的事情并获得有意义的结果,您必须阅读并理解执行计划。这就是为什么在此处提出的所有性能问题中,总是请求执行计划。
我首先要说的是,您永远不应该像这样存储数据。 最高效 的存储方式是在单独的行中,然后您可以对其进行索引。请记住,对 billion-row B-tree 的查找平均只需要 30 次查找,因此非常高效。同时,无论您对分隔数据使用什么选项,都需要进行全面扫描,这本身就比较慢。
但你已经知道了。所以只要看看你面前的两个选项:LIKE
和 STRING_SPLIT
.
感觉 LIKE
应该比 STRING_SPLIT
更快,因为枚举 table 值函数的行通常比单个标量比较慢。 LIKE
首先更复杂这一事实缓和了这一点。最重要的是,您当前拥有的代码是错误的。应该是
',' + [TableB].[TownList] + ',' LIKE ('%,' + [TableA].[Town] + ',%')
否则你在寻找 York
时会得到 New York
那么我们可以测试一下吗?是的,我们可以为脚本计时,看看哪个更快。让我们从这行开始,每行 3 个值的 2500 行。
CREATE TABLE T (v varchar(max));
INSERT T (v)
SELECT STRING_AGG(v, ';') WITHIN GROUP (ORDER BY CHECKSUM(NEWID()))
FROM (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) rn FROM master..spt_values) v
CROSS JOIN (VALUES
('123hello456'),
('123yes456'),
('1234567890thisisalongstring1234567890')
) x(v)
GROUP BY rn;
DECLARE @i int =0, @j int, @v varchar(100) = 'hello';
DECLARE @sw datetime2 = SYSDATETIME();
WHILE @i < 100
BEGIN
SELECT @j = COUNT(*)
FROM T
WHERE ';' + T.v + ';' LIKE '%;' + @v + ';%'
SET @i += 1;
END;
SELECT DATEDIFF(ms, @sw, SYSDATETIME());
DECLARE @i int =0, @j int, @v varchar(100) = 'hello';
DECLARE @sw datetime2 = SYSDATETIME();
WHILE @i < 100
BEGIN
SELECT @j = COUNT(*)
FROM T
WHERE EXISTS (SELECT 1 FROM STRING_SPLIT(T.v, ';') s WHERE s.value = @v)
SET @i += 1;
END;
SELECT DATEDIFF(ms, @sw, SYSDATETIME());
如我所料,STRING_SPLIT
较慢:db<>fiddle
让我们尝试更接近您的场景:三行非常长的 2500 个值:
CREATE TABLE T (v varchar(max));
INSERT T (v)
SELECT STRING_AGG(CAST(v AS varchar(max)), ';') WITHIN GROUP (ORDER BY CHECKSUM(NEWID()))
FROM (VALUES(1),(2),(3)) v(rn)
CROSS JOIN master..spt_values
CROSS JOIN (VALUES
('123hello456'),
('123yes456'),
('1234567890thisisalongstring1234567890')
) x(v)
GROUP BY rn;
现在 STRING_SPLIT
比 LIKE
花费的时间更长。 db<>fiddle
我想将一长串以逗号分隔的值合并为一个值,例如,假设 U.S.:
中的所有城镇像这样构造我的连接是否更有意义:
[TableA].[Town] IN (SELECT VALUE FROM SPLIT_STRING([TableB].[TownList],','))
或
',' + [TableB].[TownList] + ',' LIKE ('%,' + [TableA].[Town] + ',%')
在此示例中,[TownList]
有 3 行,每行约 300 个以逗号分隔的城镇名称。 [Town]
有大约 200,000 行各个城镇名称。
作为一个理论上的例子,我想更好地理解这些在后端做了什么,以及在哪些场景下有意义?
注意:
这是一个理论问题,可以帮助我解决现实世界的问题。
是的,我知道有更好的方法来进行此连接。
是的,我知道它需要测试,我需要知道查询计划。
从 1000 英尺的角度来看,两者之间的理论 取舍是什么?
从理论、抽象的角度来看,您正在连接两个 table,您想知道哪个连接条件优于另一个。在表达水平上,我注意到很少值得努力去分析。 CPU 依赖的东西在 SQL 服务器中似乎没有太大作用。很少看到分析表情表现的帖子。我只能回忆起其中一个大牌,表明 format
功能很昂贵。
相反,最重要的是使用的计划。这不是我们可以回答的问题;它取决于索引、table 大小和其他元数据。
因此,与所有查询一样,要了解正在发生的事情并获得有意义的结果,您必须阅读并理解执行计划。这就是为什么在此处提出的所有性能问题中,总是请求执行计划。
我首先要说的是,您永远不应该像这样存储数据。 最高效 的存储方式是在单独的行中,然后您可以对其进行索引。请记住,对 billion-row B-tree 的查找平均只需要 30 次查找,因此非常高效。同时,无论您对分隔数据使用什么选项,都需要进行全面扫描,这本身就比较慢。
但你已经知道了。所以只要看看你面前的两个选项:LIKE
和 STRING_SPLIT
.
感觉 LIKE
应该比 STRING_SPLIT
更快,因为枚举 table 值函数的行通常比单个标量比较慢。 LIKE
首先更复杂这一事实缓和了这一点。最重要的是,您当前拥有的代码是错误的。应该是
',' + [TableB].[TownList] + ',' LIKE ('%,' + [TableA].[Town] + ',%')
否则你在寻找 York
New York
那么我们可以测试一下吗?是的,我们可以为脚本计时,看看哪个更快。让我们从这行开始,每行 3 个值的 2500 行。
CREATE TABLE T (v varchar(max));
INSERT T (v)
SELECT STRING_AGG(v, ';') WITHIN GROUP (ORDER BY CHECKSUM(NEWID()))
FROM (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) rn FROM master..spt_values) v
CROSS JOIN (VALUES
('123hello456'),
('123yes456'),
('1234567890thisisalongstring1234567890')
) x(v)
GROUP BY rn;
DECLARE @i int =0, @j int, @v varchar(100) = 'hello';
DECLARE @sw datetime2 = SYSDATETIME();
WHILE @i < 100
BEGIN
SELECT @j = COUNT(*)
FROM T
WHERE ';' + T.v + ';' LIKE '%;' + @v + ';%'
SET @i += 1;
END;
SELECT DATEDIFF(ms, @sw, SYSDATETIME());
DECLARE @i int =0, @j int, @v varchar(100) = 'hello';
DECLARE @sw datetime2 = SYSDATETIME();
WHILE @i < 100
BEGIN
SELECT @j = COUNT(*)
FROM T
WHERE EXISTS (SELECT 1 FROM STRING_SPLIT(T.v, ';') s WHERE s.value = @v)
SET @i += 1;
END;
SELECT DATEDIFF(ms, @sw, SYSDATETIME());
如我所料,STRING_SPLIT
较慢:db<>fiddle
让我们尝试更接近您的场景:三行非常长的 2500 个值:
CREATE TABLE T (v varchar(max));
INSERT T (v)
SELECT STRING_AGG(CAST(v AS varchar(max)), ';') WITHIN GROUP (ORDER BY CHECKSUM(NEWID()))
FROM (VALUES(1),(2),(3)) v(rn)
CROSS JOIN master..spt_values
CROSS JOIN (VALUES
('123hello456'),
('123yes456'),
('1234567890thisisalongstring1234567890')
) x(v)
GROUP BY rn;
现在 STRING_SPLIT
比 LIKE
花费的时间更长。 db<>fiddle