Mic*_*tum 215 t-sql sql-server split comma
我有一个像这样的SQL表:
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18,20,22
| abcdef-..... | 4554a24-... | 17,19
| 987654-..... | 12324a2-... | 13,19,20
Run Code Online (Sandbox Code Playgroud)
是否有一个查询,我可以执行查询,如SELECT OtherID, SplitData WHERE SomeID = 'abcdef-.......'返回单个行,如下所示:
| OtherID | SplitData
+-------------+-------------------
| cdef123-... | 18
| cdef123-... | 20
| cdef123-... | 22
| 4554a24-... | 17
| 4554a24-... | 19
Run Code Online (Sandbox Code Playgroud)
基本上将逗号中的数据拆分成单独的行?
我知道将comma-separated字符串存储到关系数据库听起来很愚蠢,但消费者应用程序中的正常用例使得这非常有用.
我不想在应用程序中进行拆分,因为我需要分页,因此我想在重构整个应用程序之前探索选项.
这是SQL Server 2008(非R2).
Ric*_*iwi 250
您可以使用SQL Server中的精彩递归函数:
样品表:
CREATE TABLE Testdata
(
SomeID INT,
OtherID INT,
String VARCHAR(MAX)
)
INSERT Testdata SELECT 1, 9, '18,20,22'
INSERT Testdata SELECT 2, 8, '17,19'
INSERT Testdata SELECT 3, 7, '13,19,20'
INSERT Testdata SELECT 4, 6, ''
INSERT Testdata SELECT 9, 11, '1,2,3,4'
Run Code Online (Sandbox Code Playgroud)
查询
;WITH tmp(SomeID, OtherID, DataItem, String) AS
(
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM tmp
WHERE
String > ''
)
SELECT
SomeID,
OtherID,
DataItem
FROM tmp
ORDER BY SomeID
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
Run Code Online (Sandbox Code Playgroud)
产量
SomeID | OtherID | DataItem
--------+---------+----------
1 | 9 | 18
1 | 9 | 20
1 | 9 | 22
2 | 8 | 17
2 | 8 | 19
3 | 7 | 13
3 | 7 | 19
3 | 7 | 20
4 | 6 |
9 | 11 | 1
9 | 11 | 2
9 | 11 | 3
9 | 11 | 4
Run Code Online (Sandbox Code Playgroud)
Pரத*_*ீப் 126
最后,SQL Server 2016的等待已经结束.他们引入了Split字符串函数,STRING_SPLIT:
select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs
Run Code Online (Sandbox Code Playgroud)
所有其他分割字符串的方法,如XML,Tally表,while循环等等,都被这个STRING_SPLIT函数所震撼.
以下是性能比较的优秀文章:性能惊喜和假设:STRING_SPLIT.
对于旧版本,在这里使用计数表是一个拆分字符串函数(最好的方法)
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
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
;
Run Code Online (Sandbox Code Playgroud)
来自Tally OH!改进的SQL 8K"CSV分离器"功能
bvr*_*bvr 85
检查一下
SELECT A.OtherID,
Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT OtherID,
CAST ('<M>' + REPLACE(Data, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM Table1
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
Run Code Online (Sandbox Code Playgroud)
小智 18
select t.OtherID,x.Kod
from testData t
cross apply (select Code from dbo.Split(t.Data,',') ) x
Run Code Online (Sandbox Code Playgroud)
Dun*_*eon 17
很晚了,但试试这个:
SELECT ColumnID, Column1, value --Do not change 'value' name. Leave it as it is.
FROM tbl_Sample
CROSS APPLY STRING_SPLIT(Tags, ','); --'Tags' is the name of column containing comma separated values
Run Code Online (Sandbox Code Playgroud)
所以我们有这个: tbl_Sample :
ColumnID| Column1 | Tags
--------|-----------|-------------
1 | ABC | 10,11,12
2 | PQR | 20,21,22
Run Code Online (Sandbox Code Playgroud)
运行此查询后:
ColumnID| Column1 | value
--------|-----------|-----------
1 | ABC | 10
1 | ABC | 11
1 | ABC | 12
2 | PQR | 20
2 | PQR | 21
2 | PQR | 22
Run Code Online (Sandbox Code Playgroud)
谢谢!
dsz*_*dsz 12
截至2016年2月 - 参见TALLY表示例 - 从2014年2月起,很可能在下面的TVF中表现优异.保留以下原始帖子给后人:
在上面的例子中,我喜欢重复的代码太多了.我不喜欢CTE和XML的性能.此外,明确Id使订单特定的消费者可以指定一个ORDER BY子句.
CREATE FUNCTION dbo.Split
(
@Line nvarchar(MAX),
@SplitOn nvarchar(5) = ','
)
RETURNS @RtnValue table
(
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Data nvarchar(100) NOT NULL
)
AS
BEGIN
IF @Line IS NULL RETURN
DECLARE @split_on_len INT = LEN(@SplitOn)
DECLARE @start_at INT = 1
DECLARE @end_at INT
DECLARE @data_len INT
WHILE 1=1
BEGIN
SET @end_at = CHARINDEX(@SplitOn,@Line,@start_at)
SET @data_len = CASE @end_at WHEN 0 THEN LEN(@Line) ELSE @end_at-@start_at END
INSERT INTO @RtnValue (data) VALUES( SUBSTRING(@Line,@start_at,@data_len) );
IF @end_at = 0 BREAK;
SET @start_at = @end_at + @split_on_len
END
RETURN
END
Run Code Online (Sandbox Code Playgroud)
小智 9
请参考下面的TSQL。STRING_SPLIT 函数仅在兼容级别 130 及更高版本下可用。
TSQL:
DECLARE @stringValue NVARCHAR(400) = 'red,blue,green,yellow,black'
DECLARE @separator CHAR = ','
SELECT [value] As Colour
FROM STRING_SPLIT(@stringValue, @separator);
Run Code Online (Sandbox Code Playgroud)
结果:
红蓝绿黄黑
DECLARE @id_list VARCHAR(MAX) = '1234,23,56,576,1231,567,122,87876,57553,1216';
DECLARE @table TABLE ( id VARCHAR(50) );
DECLARE @x INT = 0;
DECLARE @firstcomma INT = 0;
DECLARE @nextcomma INT = 0;
SET @x = LEN(@id_list) - LEN(REPLACE(@id_list, ',', '')) + 1; -- number of ids in id_list
WHILE @x > 0
BEGIN
SET @nextcomma = CASE WHEN CHARINDEX(',', @id_list, @firstcomma + 1) = 0
THEN LEN(@id_list) + 1
ELSE CHARINDEX(',', @id_list, @firstcomma + 1)
END;
INSERT INTO @table
VALUES ( SUBSTRING(@id_list, @firstcomma + 1, (@nextcomma - @firstcomma) - 1) );
SET @firstcomma = CHARINDEX(',', @id_list, @firstcomma + 1);
SET @x = @x - 1;
END;
SELECT *
FROM @table;
Run Code Online (Sandbox Code Playgroud)
很高兴看到它已经在2016版本中得到了解决,但是对于所有那些没有解决的问题,这里是上述方法的两个通用和简化版本.
XML方法更短,但当然要求字符串允许xml-trick(没有'坏'字符.)
XML的方法:
create function dbo.splitString(@input Varchar(max), @Splitter VarChar(99)) returns table as
Return
SELECT Split.a.value('.', 'VARCHAR(max)') AS Data FROM
( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
Run Code Online (Sandbox Code Playgroud)
递归方法:
create function dbo.splitString(@input Varchar(max), @Splitter Varchar(99)) returns table as
Return
with tmp (DataItem, ix) as
( select @input , CHARINDEX('',@Input) --Recu. start, ignored val to get the types right
union all
select Substring(@input, ix+1,ix2-ix-1), ix2
from (Select *, CHARINDEX(@Splitter,@Input+@Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem from tmp where ix<>0
Run Code Online (Sandbox Code Playgroud)
功能在行动
Create table TEST_X (A int, CSV Varchar(100));
Insert into test_x select 1, 'A,B';
Insert into test_x select 2, 'C,D';
Select A,data from TEST_X x cross apply dbo.splitString(x.CSV,',') Y;
Drop table TEST_X
Run Code Online (Sandbox Code Playgroud)
XML-METHOD 2:Unicode友好(除了Max Hodges提供)
create function dbo.splitString(@input nVarchar(max), @Splitter nVarchar(99)) returns table as
Return
SELECT Split.a.value('.', 'NVARCHAR(max)') AS Data FROM
( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
我知道它有很多答案,但我想像其他人一样编写我的 split 函数版本,就像 string_split SQL Server 2016 本机函数一样。
create function [dbo].[Split]
(
@Value nvarchar(max),
@Delimiter nvarchar(50)
)
returns @tbl table
(
Seq int primary key identity(1, 1),
Value nvarchar(max)
)
as begin
declare @Xml xml = cast('<d>' + replace(@Value, @Delimiter, '</d><d>') + '</d>' as xml);
insert into @tbl
(Value)
select a.split.value('.', 'nvarchar(max)') as Value
from @Xml.nodes('/d') a(split);
return;
end;
Run Code Online (Sandbox Code Playgroud)
这是问题的答案。
CREATE TABLE Testdata
(
SomeID INT,
OtherID INT,
String VARCHAR(MAX)
);
INSERT Testdata SELECT 1, 9, '18,20,22';
INSERT Testdata SELECT 2, 8, '17,19';
INSERT Testdata SELECT 3, 7, '13,19,20';
INSERT Testdata SELECT 4, 6, '';
INSERT Testdata SELECT 9, 11, '1,2,3,4';
select t.SomeID, t.OtherID, s.Value
from Testdata t
cross apply dbo.Split(t.String, ',') s;
--Output
SomeID OtherID Value
1 9 18
1 9 20
1 9 22
2 8 17
2 8 19
3 7 13
3 7 19
3 7 20
4 6
9 11 1
9 11 2
9 11 3
9 11 4
Run Code Online (Sandbox Code Playgroud)
将 Split 与其他 split 连接
declare @Names nvarchar(max) = 'a,b,c,d';
declare @Codes nvarchar(max) = '10,20,30,40';
select n.Seq, n.Value Name, c.Value Code
from dbo.Split(@Names, ',') n
inner join dbo.Split(@Codes, ',') c on n.Seq = c.Seq;
--Output
Seq Name Code
1 a 10
2 b 20
3 c 30
4 d 40
Run Code Online (Sandbox Code Playgroud)
分两次
declare @NationLocSex nvarchar(max) = 'Korea,Seoul,1;Vietnam,Kiengiang,0;China,Xian,0';
with rows as
(
select Value
from dbo.Split(@NationLocSex, ';')
)
select rw.Value r, cl.Value c
from rows rw
cross apply dbo.Split(rw.Value, ',') cl;
--Output
r c
Korea,Seoul,1 Korea
Korea,Seoul,1 Seoul
Korea,Seoul,1 1
Vietnam,Kiengiang,0 Vietnam
Vietnam,Kiengiang,0 Kiengiang
Vietnam,Kiengiang,0 0
China,Xian,0 China
China,Xian,0 Xian
China,Xian,0 0
Run Code Online (Sandbox Code Playgroud)
拆分为列
declare @Numbers nvarchar(50) = 'First,Second,Third';
with t as
(
select case when Seq = 1 then Value end f1,
case when Seq = 2 then Value end f2,
case when Seq = 3 then Value end f3
from dbo.Split(@Numbers, ',')
)
select min(f1) f1, min(f2) f2, min(f3) f3
from t;
--Output
f1 f2 f3
First Second Third
Run Code Online (Sandbox Code Playgroud)
按范围生成行
declare @Ranges nvarchar(50) = '1-2,4-6';
declare @Numbers table (Num int);
insert into @Numbers values (1),(2),(3),(4),(5),(6),(7),(8);
with t as
(
select r.Seq, r.Value,
min(case when ft.Seq = 1 then ft.Value end) ValueFrom,
min(case when ft.Seq = 2 then ft.Value end) ValueTo
from dbo.Split(@Ranges, ',') r
cross apply dbo.Split(r.Value, '-') ft
group by r.Seq, r.Value
)
select t.Seq, t.Value, t.ValueFrom, t.ValueTo, n.Num
from t
inner join @Numbers n on n.Num between t.ValueFrom and t.ValueTo;
--Output
Seq Value ValueFrom ValueTo Num
1 1-2 1 2 1
1 1-2 1 2 2
2 4-6 4 6 4
2 4-6 4 6 5
2 4-6 4 6 6
Run Code Online (Sandbox Code Playgroud)