传递大逗号分隔值时,使用 Custom TABLE TYPE 作为参数而不是 SQL "IN" 子句更好吗

Is it better to use Custom TABLE TYPE as parameter instead of SQL "IN" clause when passing a large comma separated value

我有一个存储过程,它将逗号分隔的字符串作为输入。有时可能太大,大约超过 8000 个字符或更多。在那种情况下,查询性能有时会下降。而且我认为 IN 子句中的字符长度有限制。为此,有时我会出错。现在,我需要知道使用 Custom TABLE TYPE 作为参数并使用 Inner JOIN 来查找结果是否更好。如果是那么为什么会这样。这是我的 2 个存储过程(最少代码):

CREATE TYPE [dbo].[INTList] AS TABLE(
    [ID] [int] NULL
)

程序 1

CREATE PROCEDURE [report].[GetSKU]   
  @list [INTList] READONLY,         
AS 

Select sk.SKUID,sk.Code SCode,sk.SName
FROM SKUs sk
INNER JOIN @list sst ON sst.ID=sk.SKUID

程序 2

CREATE PROCEDURE [report].[GetSKU]   
  @params varchar(max),         
AS 
Select sk.SKUID,sk.Code SCode,sk.SName
FROM SKUs sk
WHere CHARINDEX(','+cast( sk.SKUID as varchar(MAX))+',', @params) > 0

现在,哪个程序比较好用。

注:原来的存储过程确实多了几个Joins

为了获得最佳性能,您可以使用此功能:

CREATE FUNCTION [dbo].StringSplit
(
    @String  VARCHAR(MAX), @Separator CHAR(1)
)
RETURNS @RESULT TABLE(Value VARCHAR(MAX))
AS
BEGIN     
 DECLARE @SeparatorPosition INT = CHARINDEX(@Separator, @String ),
        @Value VARCHAR(MAX), @StartPosition INT = 1
 
 IF @SeparatorPosition = 0  
  BEGIN
   INSERT INTO @RESULT VALUES(@String)
   RETURN
  END
     
 SET @String = @String + @Separator
 WHILE @SeparatorPosition > 0
  BEGIN
   SET @Value = SUBSTRING(@String , @StartPosition, @SeparatorPosition- @StartPosition)
 
   IF( @Value <> ''  ) 
    INSERT INTO @RESULT VALUES(@Value)
   
   SET @StartPosition = @SeparatorPosition + 1
   SET @SeparatorPosition = CHARINDEX(@Separator, @String , @StartPosition)
  END    
     
 RETURN
END

这个函数 return table - select * from StringSplit('12,13,14,15,16', ',') 所以你可以把这个函数加入你的 table 或者可以在 where 子句上使用 IN

由于这个问题确实在评论中引起了一些讨论,但没有得到任何可行的答案,我想补充一下要点,以帮助以后的研究。

这个问题是关于:如何将(大)值列表传递到查询中?

在大多数情况下,人们需要在 WHERE SomeColumn IN(SomeValueList)-过滤器中使用它,或者 JOIN 使用 FROM MyTable INNER JOIN SomeValueList ON....

之类的东西来反对它

非常重要的是 SQL-Server 版本,与 v2016 一样,我们有两个很棒的工具:native STRING_SPLIT()位置不安全!)和JSON支持。

此外,很明显,我们必须考虑 尺度和值

  • 我们是传入一些 ID 的简单列表还是包含数千个值的巨大列表?
  • 我们讨论的是简单整数还是 GUID?
  • 关于文本值,我们必须考虑危险字符(例如 JSON 中的 [ { " 或 XML 中的 < & - 还有更多。 ..)?
  • 关于 CSV 列表,分隔符可能出现在 内容中(引用/转义)?
  • 在某些情况下,我们甚至可能希望一次传递多个列...

有几种选择:

  • Table 值参数 (TVP, CREATE TYPE ...),
  • CSV 和字符串拆分函数(自 v2016 起原生,各种自制,CLR...),
  • 和基于文本的容器:XML 或 JSON(自 v2016 起)

Table 值参数(TVP - 最佳选择)

一个 table 赋值参数 (TVP) 必须提前创建(这可能是一个缺点)但是一旦创建就会像任何其他 table 一样工作。您可以添加索引,可以在各种用例中使用它,而不必为幕后的任何事情操心。
有时我们无法使用它,因为缺少 CREATE TYPE...

的使用权

字符分隔值 (CSV)

对于 CSV,我们看到了三种方法

  • Dynamic Sql:创建一个语句,其中 CSV 列表被简单地填充到 IN() 中并动态执行。这种 可以 是一种非常有效的方法,但会遇到各种障碍(没有 ad-hoc-使用、注入威胁、破坏不良值...)

  • 字符串拆分函数:周围有大量示例...它们的共同点是分离的字符串将作为项目列表返回。此处的常见问题:性能、缺少序号位置、分隔符的限制、重复值或空值的处理、引号或转义值的处理、内容 分隔符的处理。 Aaron Bertrand 对字符串拆分的各种方法做了一些 great research。与TVP类似,一个缺点可能是,这个函数必须提前存在于数据库中,否则我们需要被允许执行CREATE FUNCTION

  • ad-hoc-splitters:在 v2016 之前,最常用的方法是基于 XML,从那时起我们已经转向基于 JSON 的拆分器。两者都使用一些字符串方法将 CSV 字符串转换为 1) 分隔元素 (XML) 或 2) 转换为 JSON 数组。结果由 1) XQuery (.value().nodes()) 或 2) JSON 的 OPENJSON()JSON_VALUE().

    查询

基于文本的容器

我们可以将列表作为字符串传递,但在定义的格式内:

  • 使用 ["a","b","c"] 而不是 a,b,c 可以立即使用 OPENJSON().
  • 改为使用 <x>a</x><x>b</x><x>c</x> 允许 XML 查询。

这里最大的优势:任何编程语言都提供对这些格式的支持。
隐式解决了日期和数字格式等常见障碍。在大多数情况下,传递 JSON 或 XML 只是几行代码。
这两种方法都允许类型和位置安全的查询。
我们可以解决我们的需求,而不需要依赖任何预先存在的东西。