传递大逗号分隔值时,使用 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 只是几行代码。
这两种方法都允许类型和位置安全的查询。
我们可以解决我们的需求,而不需要依赖任何预先存在的东西。
我有一个存储过程,它将逗号分隔的字符串作为输入。有时可能太大,大约超过 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 只是几行代码。
这两种方法都允许类型和位置安全的查询。
我们可以解决我们的需求,而不需要依赖任何预先存在的东西。