Включение строки, разделенной запятыми, в отдельные строки
У меня таблица SQL такая:
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18,20,22
| abcdef-..... | 4554a24-... | 17,19
| 987654-..... | 12324a2-... | 13,19,20
есть ли запрос, в котором я могу выполнить такой запрос, как SELECT OtherID, SplitData WHERE SomeID = 'abcdef-.......'
который возвращает отдельные строки, например:
| OtherID | SplitData
+-------------+-------------------
| cdef123-... | 18
| cdef123-... | 20
| cdef123-... | 22
| 4554a24-... | 17
| 4554a24-... | 19
В основном разделены мои данные в запятой в отдельные строки?
Я знаю, что сохранение comma-separated
строки в реляционной базе данных звучит глупо, но нормальный вариант использования в потребительском приложении делает это действительно полезным.
Я не хочу делать раскол в приложении, поскольку мне нужен пейджинг, поэтому я хотел изучить варианты перед рефакторингом всего приложения.
Это SQL Server 2008
(не R2).
Ответы
Ответ 1
Вы можете использовать замечательные рекурсивные функции из 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'
Запрос
;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
Вывод
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
Ответ 2
Наконец, ожидание закончилось с SQL Server 2016. Они ввели функцию строки Split, STRING_SPLIT
:
select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs
Все остальные методы разделения строки, такие как XML, таблица Tally, while loop и т.д., были удалены этой функцией 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
;
Относится к Tally OH! Улучшенная функция SQL 8K "CSV Splitter"
Ответ 3
Проверьте это
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);
Ответ 4
select t.OtherID,x.Kod
from testData t
cross apply (select Code from dbo.Split(t.Data,',') ) x
Ответ 5
По состоянию на февраль 2016 - см. таблицу TALLY Table Example - очень вероятно, чтобы превзойти мой TVF ниже, с февраля 2014 года. Сохраняя исходное сообщение ниже для потомков:
Слишком много повторяющегося кода для моей симпатии в приведенных выше примерах. И мне не нравится производительность 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 @[email protected]_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
Ответ 6
Приятно видеть, что оно было решено в версии 2016 года, но для всех тех, что на этом нет, вот две обобщенные и упрощенные версии вышеприведенных методов.
XML-метод короче, но, конечно, требуется строка, позволяющая xml-трюк (нет "плохих" символов.)
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);
Рекурсивный метод:
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,@[email protected],ix+1) ix2 from tmp) x where ix2<>0
) select DataItem from tmp where ix<>0
Функция в действии
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
XML-МЕТОД 2: Юникод Дружественный < (добавление любезно предоставлено 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);
Ответ 7
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
Ответ 8
При использовании этого подхода вы должны убедиться, что ни одно из ваших значений не содержит что-то, что было бы незаконным. XML - user1151923
Я всегда использую метод XML. Убедитесь, что вы используете VALID XML. У меня есть две функции для преобразования между действительным XML и текстом. (Я склоняюсь к тому, чтобы вырезать каретки, как мне обычно не нужны.
CREATE FUNCTION dbo.udf_ConvertTextToXML (@Text varchar(MAX))
RETURNS varchar(MAX)
AS
BEGIN
SET @Text = REPLACE(@Text,CHAR(10),'')
SET @Text = REPLACE(@Text,CHAR(13),'')
SET @Text = REPLACE(@Text,'<','<')
SET @Text = REPLACE(@Text,'&','&')
SET @Text = REPLACE(@Text,'>','>')
SET @Text = REPLACE(@Text,'''',''')
SET @Text = REPLACE(@Text,'"','"')
RETURN @Text
END
CREATE FUNCTION dbo.udf_ConvertTextFromXML (@Text VARCHAR(MAX))
RETURNS VARCHAR(max)
AS
BEGIN
SET @Text = REPLACE(@Text,'<','<')
SET @Text = REPLACE(@Text,'&','&')
SET @Text = REPLACE(@Text,'>','>')
SET @Text = REPLACE(@Text,''','''')
SET @Text = REPLACE(@Text,'"','"')
RETURN @Text
END
Ответ 9
функция
CREATE FUNCTION dbo.SplitToRows (@column varchar(100), @separator varchar(10))
RETURNS @rtnTable TABLE
(
ID int identity(1,1),
ColumnA varchar(max)
)
AS
BEGIN
DECLARE @position int = 0
DECLARE @endAt int = 0
DECLARE @tempString varchar(100)
set @column = ltrim(rtrim(@column))
WHILE @position<=len(@column)
BEGIN
set @endAt = CHARINDEX(@separator,@column,@position)
if(@endAt=0)
begin
Insert into @rtnTable(ColumnA) Select substring(@column,@position,len(@column)[email protected])
break;
end
set @tempString = substring(ltrim(rtrim(@column)),@position,@[email protected])
Insert into @rtnTable(ColumnA) select @tempString
set @[email protected]+1;
END
return
END
Случай использования
select * from dbo.SplitToRows('T14; p226.0001; eee; 3554;', ';')
Или просто выбор с несколькими наборами результатов
DECLARE @column varchar(max)= '1234; 4748;abcde; 324432'
DECLARE @separator varchar(10) = ';'
DECLARE @position int = 0
DECLARE @endAt int = 0
DECLARE @tempString varchar(100)
set @column = ltrim(rtrim(@column))
WHILE @position<=len(@column)
BEGIN
set @endAt = CHARINDEX(@separator,@column,@position)
if(@endAt=0)
begin
Select substring(@column,@position,len(@column)[email protected])
break;
end
set @tempString = substring(ltrim(rtrim(@column)),@position,@[email protected])
select @tempString
set @[email protected]+1;
END
Ответ 10
;WITH tmp(SomeID, OtherID, DataItem, Data) as (
SELECT SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
FROM Testdata
WHERE Data > ''
)
SELECT SomeID, OtherID, Data
FROM tmp
ORDER BY SomeID
с небольшим небольшим изменением в вышеприведенном запросе...
Ответ 11
Ниже работает на SQL Server 2008
select *, ROW_NUMBER() OVER(order by items) as row#
from
( select 134 myColumn1, 34 myColumn2, 'd,c,k,e,f,g,h,a' comaSeperatedColumn) myTable
cross apply
SPLIT (rtrim(comaSeperatedColumn), ',') splitedTable -- gives 'items' column
Получит все декартово произведение с столбцами таблицы происхождения и "элементами" таблицы разбиения.
Ответ 12
См. Ниже TSQL. Функция STRING_SPLIT доступна только при уровне совместимости 130 и выше.
TSQL:
DECLARE @stringValue NVARCHAR (400) = 'красный, синий, зеленый, желтый, черный'
DECLARE @separator CHAR = ','
SELECT [значение] В качестве цвета FROM STRING_SPLIT (@stringValue, @separator);
РЕЗУЛЬТАТ:
Цвет
красный синий зеленый желтый черный