与中的WHERE一起使用的SQL分解字符串

与中的WHERE一起使用的SQL分解字符串,sql,sql-server,where-in,Sql,Sql Server,Where In,我在SQL Server中有一个表,其中有一列存储一系列字符。我希望能够向存储过程发送一系列字符,并将其用于WHERE IN。下面的例子 ID | series ---+------- 1 | U 2 | B 3 | R 4 | UB 5 | BR 我想要一份如下的报税表: Parameter | Return IDs ----------+------------ U | 1 R | 3 U, R | 1, 3 我可以以任何方式格式化参数

我在SQL Server中有一个表,其中有一列存储一系列字符。我希望能够向存储过程发送一系列字符,并将其用于WHERE IN。下面的例子

ID | series
---+-------
 1 | U
 2 | B
 3 | R
 4 | UB
 5 | BR
我想要一份如下的报税表:

Parameter | Return IDs
----------+------------
 U        | 1
 R        | 3
 U, R     | 1, 3

我可以以任何方式格式化参数,UR或U、R或任何形式。我可以在应用程序中对此进行分解并调用该过程N次,但我不希望这样,以便我可以在查询中使用order by。

实现这一点的方法是传入一个带分隔符的字符串,然后创建一个函数以接收参数,拆分值并返回一个表值。然后,您所需要做的就是创建一个类似于的存储过程

CREATE PROCEDURE MyProc
(
    @MyParameter NVARCHAR(3000)
)
AS

SELECT 
    ID,
    Series
FROM
    MyTable
WHERE
    Series IN (SELECT ID FROM dbo.MyFunctionToSplitDelimitedString(@MyParameter,',')
如果不能使用表值参数,这里有一种方法

declare @table table (ID int, series varchar(16))
insert into @table
values
(1,'U'),
(2,'B'),
(3,'R'),
(4,'UB'),
(5,'BR')


declare @input varchar(4000) = 'U,R'

select
    t.*
from @table t
cross apply dbo.DelimitedSplit8K(@input,',') x 
where
x.Item = t.series
或无交叉应用

这已被证明是一种快速分割字符串的方法:


SQL Server可以接受表类型作为存储过程参数—然后存储过程可以利用联接和其他RA/SET操作。这也将是“更多的SQL”,并且更容易期望输出本身被规范化。例如,使用指定为表类型的集合{U,R}进行调用将产生{R,3},{U,1}两个规范化行。然后,应用程序可以将结果转换为适当的输出格式,以满足自己的需要。与填充到非规范化输出相关,我仍然建议不要这样做。还可以编写一个UDF聚合函数。IIRC,SQL Server 2016有这样一个聚合,尽管我没有想到。如果您的应用程序是用支持它的语言编写的,那么表值参数就是最好的选择。
;with cte as
(select * 
from dbo.DelimitedSplit8K(@input,',') x )

select
    t.*
from @table t
inner join cte on Item = t.series 
CREATE FUNCTION [dbo].[DelimitedSplit8K] (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!

RETURNS TABLE WITH SCHEMABINDING AS
RETURN

/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/

  WITH E1(N) AS (
                 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
                ),                          --10E+1 or 10 rows
       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 (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 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
;
GO