将多个逗号分隔的字符串存储到临时 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;
给定的字符串:
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;