[sql-server] 쉼표로 구분 된 문자열을 개별 행으로 변환

다음과 같은 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).



답변

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        


답변

마지막으로 SQL Server 2016 에서 대기가 끝났습니다 . 그들은 문자열 분리 기능을 도입했습니다 STRING_SPLIT:

select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs

XML, Tally 테이블, while 루프 등과 같은 문자열을 분할하는 다른 모든 방법은이 STRING_SPLIT함수에 의해 사라졌습니다 .

여기에 성능 비교와 훌륭한 기사입니다 성능 놀라움과 가정 : STRING_SPLIT .

이전 버전의 경우 Tally Table을 사용하면 하나의 분할 문자열 함수가 있습니다 (최상의 접근 방식)

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 스플리터”기능


답변

이것을 확인하십시오

 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); 


답변

select t.OtherID,x.Kod
    from testData t
    cross apply (select Code from dbo.Split(t.Data,',') ) x


답변

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


답변

2016 버전에서 해결되었음을 알았지 만 그 위에없는 모든 사람들을 위해 위의 방법에 대한 두 가지 일반화되고 단순화 된 버전이 있습니다.

XML 방법은 더 짧지 만 물론 xml-trick을 허용하는 문자열이 필요합니다 ( ‘bad’문자 없음).

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,@Input+@Splitter,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-METHOD 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);


답변

아래 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); 

결과:

색깔

빨강 파랑 녹색 노랑 검정