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

时间:2018-11-25 08:55:17

标签: sql-server tsql sql-server-2008-r2

给出字符串:

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

要以以下方式存储到临时表中:

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

尝试:创建了名为udf_split的用户定义函数,并为每列插入表中。

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    

2 个答案:

答案 0 :(得分:3)

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

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

对于这种事情,您想使用一个拆分函数,该函数同时返回项目及其在原始字符串中的位置。幸运的是,杰夫·摩登(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;

答案 1 :(得分:2)

我的建议使用两个独立的递归拆分。这使我们可以使用位置索引将两个字符串转换为两组。最后的SELECT将把这两个集合放在其位置索引上,并返回一个排序列表:

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()以外,此方法还返回key作为数组中元素的从零开始的索引。结果是一样的...