T-SQL 분할 문자열


139

쉼표로 분리해야하는 문자열이 포함 된 SQL Server 2008 R2 열이 있습니다. StackOverflow에서 많은 답변을 보았지만 R2에서는 아무것도 작동하지 않습니다. 스플릿 함수 예제에 대한 선택 권한이 있는지 확인했습니다. 어떤 도움이라도 대단히 감사합니다.


7
이렇게 만 대답 중 하나입니다 I 같은 stackoverflow.com/a/1846561/227755
nurettin

2
"아무도 작동하지 않는다"는 것은 무엇을 의미합니까? 더 자세하게 얘기해 주 시겠어요?
Aaron Bertrand

Andy가 함수를 잘못 실행했을 때 올바른 방향으로 나를 가리 켰습니다. 이것이 다른 스택 응답 중 아무것도 작동하지 않은 이유입니다. 내 잘못.
Lee Grindon


있다 mdq.RegexSplit"마스터 데이터 서비스"추가 기능, 도움이 될 수있는 기능. 확실히 조사 할 가치가 있습니다.
jpaugh

답변:


233

나는 당신을 위해 작동하기 전에이 SQL을 사용했다 :

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

그것을 사용하려면 :-

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')

1
좋은 점은 이것이 바로 제가 찾고 있던 것입니다
Lee Grindon

2
고마워 앤디. 함수가 분할 문자열의 특정 색인에서 항목을 반환 할 수 있도록 스크립트를 약간 개선했습니다. 열의 구조가 구문 분석되는 상황에서만 유용합니다. gist.github.com/klimaye/8147193
CF_Maintainer

1
내 GitHub의 페이지 (테스트 케이스를 백업으로) 몇 가지 개선 사항을 게시 여기에 . 게시물 "보호"를 초과 할만큼 충분한 담당자가있을 때이 스택 오버 플로우 스레드에 답변으로 게시합니다.
mpag

8
이것은 훌륭한 답변이지만 구식입니다 ... 절차 적 접근 방식 (특히 루프)은 피해야 할 것입니다 ... 더 새로운 답변을 살펴볼 가치가 있습니다 ...
Shnugo

2
@Shnugo에 전적으로 동의합니다. 루핑 스플리터는 작동하지만 끔찍하게 느립니다. 이 sqlservercentral.com/articles/Tally+Table/72993 과 같은 것이 훨씬 좋습니다. 다른 우수한 세트 기반 옵션은 여기에서 찾을 수 있습니다. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

61

재귀 적 CTE와 while 루프 대신 더 셋 기반 접근 방식을 고려한 사람이 있습니까? 이 함수는 질문을 위해 작성되었으며 SQL Server 2008과 쉼표를 구분 기호로 사용했습니다 . SQL Server 2016 이상 (및 호환성 수준 130 이상)에서 STRING_SPLIT()더 나은 옵션 입니다.

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE
AS
  RETURN ( SELECT [Value] FROM 
  ( 
    SELECT [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
      CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
    FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
      FROM sys.all_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

문자열 길이가 <= 행 수 sys.all_columns( modelSQL Server 2017에서는 9,980, 사용자 데이터베이스에서는 훨씬 높음)로 제한하지 않으려면 다음과 같은 다른 방법을 사용하여 숫자를 도출 할 수 있습니다. 자신 의 숫자 테이블 만들기 . 시스템 테이블을 사용하거나 직접 테이블을 만들 수없는 경우 재귀 CTE를 사용할 수도 있습니다.

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

그러나 문자열이 100자를 초과하는 재귀 오류를 피하려면 외부 쿼리에 OPTION (MAXRECURSION 0)(또는 MAXRECURSION <longest possible string length if < 32768>) 를 추가 해야합니다. 이것이 좋은 대안 아니라면 이 답변 을 참조하십시오 의견에서 지적한 대로이 .

(또한 분리 문자는 NCHAR(<=1228). 왜인지 연구해야합니다.)

분할 함수에 대한 자세한 내용, while 루프 및 재귀 CTE가 확장되지 않는 이유 (및 증명) 및 애플리케이션 계층에서 문자열을 분할하는 경우 더 나은 대안 :


1
최종 값이 파싱되지 않기 때문에 문자열 끝에 null 값이있는 경우 (예 : '1,2,, 4')이 절차에는 작은 버그가 있습니다. 이 버그를 수정하려면 "WHERE Number <= LEN (@List)"식을 "WHERE Number <= LEN (@List) + 1"로 바꿔야합니다.
SylvainL

@SylvainL 나는 그것이 당신이 원하는 행동에 달려 있다고 생각합니다. 내 경험상 대부분의 사람들은 실제로 실제 요소를 나타내지 않기 때문에 후행 쉼표를 무시하고 싶습니다 (빈 문자열의 사본 수는 몇 개입니까)? 어쨌든, 이 작업을 수행 하는 실제 방법은 두 번째 링크를 따라 가면 어쨌든 느린 T-SQL에서 큰 못생긴 문자열을 분할하여 혼란스럽게하는 것입니다.
Aaron Bertrand

1
당신이 말했듯이, 대부분의 사람들은 후행 쉼표를 무시하고 싶지만 모두 아는 것은 아닙니다. 더 완벽한 해결책은이 경우에 수행 할 작업을 지정하기 위해 매개 변수를 추가하는 것이라고 생각하지만 내 의견은 많은 경우에 실제로 일어날 수 있기 때문에 아무도이 가능성을 잊어 버리지 않도록 약간의 메모입니다.
SylvainL

그 기능에 이상한 행동이 있습니다. 문자열을 매개 변수로 직접 사용하면 작동합니다. varchar가 있으면 그렇지 않습니다. 쉽게 재현 할 수 있습니다 : invarchar를 varchar로 선언 invarchar = 'ta; aa; qq'[dbo]의 SELECT 값. [SplitString] (invarchar, ';') [dbo]의 SELECT 값. [SplitString] ( 'ta; aa; qq ','; ')
Patrick Desjardins 2016 년

이 방법이 마음에 들지만 반환되는 객체 sys.all_objects수가 입력 문자열의 문자 수보다 적 으면 문자열이 잘리고 값이 누락됩니다. sys.all_objects행을 생성하는 데 약간의 핵으로 사용 되기 때문에 더 좋은 방법이 있습니다 (예 : this answer) .
너클

56

마지막으로 SQL Server 2016 에서 대기가 끝났습니다. 분할 문자열 기능이 도입되었습니다.STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

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

성능 비교를 통한 훌륭한 기사는 다음과 같습니다. 성능 놀라움 및 가정 : STRING_SPLIT


5
서버가 업데이트 된 사람들을 위해 문자열을 나누는 방법에 대한 질문에 분명히 대답하지만 2008 / 2008R2에 여전히 붙어있는 사람들은 다른 답변 중 하나와 함께 가야합니다.
mpag

2
데이터베이스의 호환성 수준을 살펴 봐야합니다. 130보다 낮 으면 STRING_SPLIT 기능을 사용할 수 없습니다.
Luis Teijon 2016 년

실제로 호환성이 130이 아니고 2016 (또는 Azure SQL)을 실행중인 경우 다음을 사용하여 호환성을 130까지 설정할 수 있습니다. ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Michieal

23

가장 쉬운 방법은 XML 형식 입니다.

1. 문자열을 테이블없이 행으로 변환

질문

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

결과

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. 각 CSV 행에 대한 ID가있는 테이블에서 행으로 변환

소스 테이블

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

질문

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

결과

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x

@String금지 된 문자 가 포함 된 경우이 방법이 중단됩니다 .이 문제를 극복하기 위해 방금 답변 을 게시 했습니다 .
Shnugo

9

나는 제거하는 빠른 방법이 필요 +4에서 우편 번호를 .

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

프로세스 없음 ... UDF 없음 ... 꼭 필요한 일을하는 하나의 꽉 작은 인라인 명령. 화려하지 않고 우아하지 않습니다.

필요에 따라 구분 기호를 변경하면 모든 것이 가능합니다.


4
이것은 질문에 관한 것이 아닙니다. OP에는 '234,542,23'과 같은 값이 있으며 세 행으로 나눕니다 ... 1 행 : 234, 2 행 : 542, 3 행 : 23. SQL에서 수행하기 까다로운 작업입니다.
codeulike

7

교체하면

WHILE CHARINDEX(',', @stringToSplit) > 0

WHILE LEN(@stringToSplit) > 0

while 루프 후 마지막 인서트를 제거 할 수 있습니다!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END

이로 인해 마지막 요소의 마지막 문자가 잘립니다. 즉 "AL, AL"은 "AL"이됩니다. | "A", 즉 "ABC, ABC, ABC"는 "ABC"가됩니다 | "ABC"| "AB"
Microsoft 개발자

이 문제를 해결하기 위해 추가 +1하는 SELECT @pos = LEN(@stringToSplit)것처럼 보입니다. 그러나 SUBSTRING의 세 번째 매개 변수 를 추가하지 않으면가 SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)반환 Invalid length parameter passed to the LEFT or SUBSTRING function됩니다 +1. 또는 해당 과제를 다음과 같이 대체 할 수 있습니다.SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag

1
내 GitHub의 페이지 (테스트 케이스를 백업으로) 몇 가지 개선 사항을 게시 여기에 . 게시물 "보호"를 초과 할만큼 충분한 담당자가있을 때이 스택 오버 플로우 스레드에 답변으로 게시합니다.
mpag

나도 테리가 지적한 문제에 주목했다. 그러나 @AviG의 주어진 논리는 너무 멋져 긴 토큰 목록을 위해 중간에 실패하지 않습니다. 이 테스트 호출을 시도하여 확인하십시오 (이 호출은 969 토큰을 반환해야 함) select * from dbo.splitstring ( 'token1, token2 ,,,,,,,, token969') 그런 다음 mpag에서 제공 한 코드로 동일한 결과를 확인했습니다. 위에 전화하여 365 토큰 만 반환 할 수 있음을 발견했습니다. 마지막으로 위의 AviG 코드를 수정하고 여기에 주석이 제한된 텍스트 만 허용하기 때문에 버그가없는 기능을 새로운 회신으로 게시했습니다. 내 이름으로 답장을 확인하십시오.
Gemunu R Wickremasinghe 2018 년

3

어떤 종류의 반복 (반복)을 사용하는 문자열 분할의 모든 기능은 성능이 떨어집니다. 세트 기반 솔루션으로 교체해야합니다.

이 코드는 훌륭하게 실행됩니다.

CREATE FUNCTION dbo.SplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO

@List금지 된 문자 가 포함 된 경우이 방법이 중단됩니다 .이 문제를 극복하기 위해 방금 답변 을 게시 했습니다 .
Shnugo

귀하의 응답은 구분 기호로 사용되며 가장 높은 투표를 한 사람이 아닌 공간으로 작업하기 때문에 귀하의 답변을지지합니다.
KMC

3

금지 된 문자의 경우 XML 요소에 자주 사용되는 접근 방식이 중단됩니다. 이것은 세미콜론을 구분 기호로 사용하더라도 모든 문자에이 방법을 사용하는 방법입니다.

트릭은 먼저 SELECT SomeString AS [*] FOR XML PATH('')모든 금지 된 문자를 올바르게 이스케이프 처리하는 데 사용하는 것입니다. 그 이유 때문에 구분 기호를 문제를 피하기 위해 마법의 값 으로; 입니다.

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

결과

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F

2

최근에 이런 식으로 글을 써야했습니다. 내가 생각해 낸 해결책은 다음과 같습니다. 구분 기호 문자열에 대해 일반화되어 있으며 약간 더 나은 성능을 발휘할 것으로 생각합니다.

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END

1

누군가가 필요로하는 경우 CTE를 사용하는 솔루션 (나를 제외하고는 분명히 내가 작성한 이유입니다).

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit

1

이것은 더 좁게 조정됩니다. 이 작업을 수행 할 때 일반적으로 쉼표로 구분 된 고유 ID 목록 (INT 또는 BIGINT)이 있는데, 기본 키가 INT 또는 BIGINT 인 다른 테이블에 대한 내부 조인으로 사용할 테이블로 캐스트하려고합니다. 가장 효율적인 조인이 가능하도록 인라인 테이블 반환 함수가 반환되기를 원합니다.

샘플 사용법은 다음과 같습니다.

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html 에서 아이디어를 훔쳐 인라인 테이블 값으로 변경하고 INT로 캐스팅했습니다.

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO

1

여기에 올바른 버전이 있지만 끝에 쉼표가 있고 기능으로 사용하지 않고 더 큰 코드의 일부로 사용할 수 있도록 작은 내결함성을 추가하는 것이 좋을 것이라고 생각했습니다. . 한 번만 사용하고 기능이 필요하지 않은 경우를 대비하여. 이것은 또한 정수 (필요한 것)이므로 데이터 유형을 변경해야 할 수도 있습니다.

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs

만약 당신이 루프 SET @StringToSeperate = @StringToSeperate+','바로 앞에 있다면 WHILE"add last value"블록을 제거 할 수 있다고 생각합니다. 또한 github에 대한
의견

이것은 어느 대답을 기반으로합니까? 여기에 많은 답변이 있으며 약간 혼란 스럽습니다. 감사.
jpaugh

1

+ 앤디 로빈슨의 기능을 약간 수정했습니다. 이제 반품 테이블에서 필요한 부품 만 선택할 수 있습니다.

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5


1

최소 코드로 일반적인 경우에 빠른 임시 솔루션이 필요한 경우이 재귀 적 CTE 2- 라이너가이를 수행합니다.

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

이것을 독립형 진술로 사용하거나 위의 CTE를 쿼리에 추가하면 결과 테이블을 조인 할 수 있습니다 b 추가하면 다른 식과 추가 표현식에 사용할 수 있습니다.

편집하다 (Shnugo 작성)

카운터를 추가하면 목록과 함께 위치 인덱스가 표시됩니다.

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

결과:

n   s
1   1
2   2333
3   344
4   4

나는이 접근법을 좋아한다. 나는 당신의 대답에 직접 약간의 향상을 추가했기 때문에 신경 쓰지 않기를 바랍니다. 편리한 방법으로 편하게 편집하십시오.
Shnugo

1

값을 요소로 래핑하여 xml 경로를 사용합니다 (M이지만 아무 효과가 있습니다).

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)

0

다음은 위의 게시물을 간단히 수정 한 patindex를 사용하여 패턴으로 분할 할 수있는 버전입니다. 여러 구분 기호 문자가 포함 된 문자열을 분할 해야하는 경우가있었습니다.


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

결과는 다음과 같습니다

현악기 현악기 x x z


0

개인적으로 나는이 기능을 사용합니다 :

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)

0

요청에 따라 나는 (두 개의 분리 문자 이동합니다) 이중 분배기를 개발 한 다음 . 이 스레드에서 문자열 분할과 관련된 쿼리에 가장 많이 참조되는 값을 볼 수 있습니다.

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

용법:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

가능한 사용법 (각 분할의 두 번째 값을 얻음) :

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2

0

다음은 함수로 사용하거나 동일한 논리를 프로 시저에 넣을 수있는 예입니다. --SELECT * from [dbo] .fn_SplitString;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;

@vCSV금지 된 문자 가 포함 된 경우이 방법이 중단됩니다 .이 문제를 극복하기 위해 방금 답변 을 게시 했습니다 .
Shnugo

0

재귀 cte 기반 솔루션

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;

0

이것은 Andy Robertson의 답변을 기반으로하며 쉼표 이외의 구분 기호가 필요했습니다.

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

그리고 그것을 사용하려면 :

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(SQL Server 2008 R2에서 테스트)

편집 : 올바른 테스트 코드


0

/ *

대답 T-SQL 분할 문자열
에서 답변을 바탕으로 앤디 로빈슨AviG는
기능 심판을 강화 : SQL 서버에 후행 공백을 포함하지 LEN 기능
이 '파일'은 인하 파일과 SQL 파일을 모두 유효해야한다


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

테스트 사례 : 위의 "향상된 기능"으로 참조되는 URL 참조

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

* /


"이 '파일'은 마크 다운 파일과 SQL 파일로 유효해야합니다."
mpag

-1
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END

-1

이 기능을 사용할 수 있습니다 :

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO

-1

@AviG와 관련하여 이것은 모든 토큰을 완전히 반환하기 위해 버그가없는 버전의 기능입니다.

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END

-3

가장 쉬운 방법 :

  1. SQL Server 2016 설치
  2. STRING_SPLIT https://msdn.microsoft.com/en-us/library/mt684588.aspx 사용

그것은 익스프레스 에디션에서도 작동합니다 :).


"호환성 수준"을 SQL Server 2016 (130)으로 설정하는 것을 잊지 마십시오. 관리 스튜디오에서 데이터베이스, 속성 / 옵션 / 호환성 수준을 마우스 오른쪽 버튼으로 클릭하십시오.
Tomino

1
원래 게시물은 SQL 2008 R2에 대해 말했습니다. SQL 2016 설치는 옵션이 아닐 수 있습니다
Shawn Gavett
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.