对于 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 次查找,因此非常高效。同时,无论您对分隔数据使用什么选项,都需要进行全面扫描,这本身就比较慢。


但你已经知道了。所以只要看看你面前的两个选项:LIKESTRING_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_SPLITLIKE 花费的时间更长。 db<>fiddle