将多个逗号分隔的字符串存储到临时 table

Store multiple comma separated strings into temp table

给定的字符串:

string 1: 'A,B,C,D,E'
string 2: 'X091,X089,X051,X043,X023'

想要存储到临时 table 中:

String1     String2
---------------------
A           X091
B           X089
C           X051
D           X043
E           X023

已尝试:创建了名为 udf_split 的用户定义函数并为每一列插入 table。

DECLARE @Str1 VARCHAR(MAX) = 'A,B,C,D,E'
DECLARE @Str2 VARCHAR(MAX) = 'X091,X089,X051,X043,X023'

IF OBJECT_ID('tempdb..#TestString') IS NOT NULL DROP TABLE #TestString;

CREATE TABLE #TestString (string1 varchar(100),string2 varchar(100));

INSERT INTO #TestString(string1) SELECT Item FROM udf_split(@Str1,',');
INSERT INTO #TestString(string2) SELECT Item FROM udf_split(@Str2,',');

但得到以下结果:

SELECT * FROM #TestString

string1 string2
-----------------
A       NULL
B       NULL
C       NULL
D       NULL
E       NULL
NULL    X091
NULL    X089
NULL    X051
NULL    X043
NULL    X023    

我的建议是使用两个独立的递归拆分。这允许我们使用位置索引将两个字符串转换为两个集合。最终的 SELECT 将在它们的位置索引和 return 排序列表中加入两个集合:

DECLARE @str1 VARCHAR(1000)= 'A,B,C,D,E';
DECLARE @str2 VARCHAR(1000)= 'X091,X089,X051,X043,X023';

;WITH
 --split the first string
 a1 AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @str1, j+1) FROM a1 WHERE j > i)
,b1 AS (SELECT n, SUBSTRING(@str1, i+1, IIF(j>0, j, LEN(@str1)+1)-i-1) s FROM a1 WHERE i >= 0)
 --split the second string
,a2 AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @str2, j+1) FROM a2 WHERE j > i)
,b2 AS (SELECT n, SUBSTRING(@str2, i+1, IIF(j>0, j, LEN(@str2)+1)-i-1) s FROM a2 WHERE i >= 0)
--join them by the index
SELECT b1.n
      ,b1.s AS s1
      ,b2.s AS s2
FROM b1
INNER JOIN b2 ON b1.n=b2.n
ORDER BY b1.n;

结果

n   s1  s2
1   A   X091
2   B   X089
3   C   X051
4   D   X043
5   E   X023

更新:如果您有 v2016+...

使用 SQL-Server 2016+ you can use OPENJSON 进行微小的字符串替换,将 CSV 字符串转换为 JSON-数组:

SELECT a.[key]
      ,a.value AS s1
      ,b.value AS s2
FROM OPENJSON('["' + REPLACE(@str1,',','","') + '"]') a
INNER JOIN(SELECT * FROM OPENJSON('["' + REPLACE(@str2,',','","') + '"]')) b ON a.[key]=b.[key]
ORDER BY a.[key];

不同于 STRING_SPLIT() 这种方法 return 将 key 作为 数组中元素的从零开始的索引 。结果是一样的...

您需要将每一行的两个部分一起插入。否则,您最终会得到每一行都有一列包含 null - 这正是您尝试时发生的情况。

首先,如果可能的话,我建议根本不要乱用数据库中的逗号分隔字符串。
如果您可以控制输入,则最好使用 table 变量或 xml。 如果你不这样做,要在 2016 年以下的任何版本中拆分字符串,我建议首先阅读 Aaron Bertrand 的 Split strings the right way – or the next best way。在 2016 年,您应该使用内置的 string_split 函数。

对于这种事情,您需要使用 returns 项目及其在原始字符串中的位置的拆分函数。幸运的是,Jeff Moden 已经编写了这样一个拆分函数,它是一个非常流行的高性能函数。
您可以在 Tally OH! An Improved SQL 8K “CSV Splitter” Function.

上阅读所有相关信息

所以,这是 Jeff 的函数:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@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
;

下面是你如何使用它:

DECLARE @Str1 VARCHAR(MAX) = 'A,B,C,D,E'
DECLARE @Str2 VARCHAR(MAX) = 'X091,X089,X051,X043,X023'

IF OBJECT_ID('tempdb..#TestString') IS NOT NULL DROP TABLE #TestString;

CREATE TABLE #TestString (string1 varchar(100),string2 varchar(100));

INSERT INTO #TestString(string1, string2) 
SELECT A.Item, B.Item
FROM DelimitedSplit8K(@Str1,',') A
JOIN DelimitedSplit8K(@Str2,',') B
ON A.ItemNumber = B.ItemNumber;