[sql] 항목 x에 액세스 할 수 있도록 문자열을 어떻게 분할합니까?

SQL Server를 사용하여 항목 x에 액세스 할 수 있도록 문자열을 어떻게 분할합니까?

“Hello John Smith”문자열을 가져옵니다. 문자열을 공백으로 분할하고 인덱스 1에서 “John”을 반환해야하는 항목에 어떻게 액세스 할 수 있습니까?



답변

구분 된 문자열을 구문 분석하는 SQL 사용자 정의 함수 의 솔루션이 도움이 될 수 있습니다 ( 코드 프로젝트 ).

이 간단한 논리를 사용할 수 있습니다.

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%', @products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products,
                                    0,
                                    PATINDEX('%|%', @products))
        SELECT @individual

        SET @products = SUBSTRING(@products,
                                  LEN(@individual + '|') + 1,
                                  LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END


답변

SQL Server에 내장 분할 기능이 있다고 생각하지 않으므로 UDF 이외의 다른 대답은 PARSENAME 함수를 납치하는 것입니다.

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 

PARSENAME은 문자열을 가져 와서 마침표 문자로 분할합니다. 두 번째 인수로 숫자를 사용하며 그 숫자는 반환 할 문자열의 세그먼트를 지정합니다 (뒤에서 앞으로).

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

명백한 문제는 문자열에 이미 마침표가 포함되어있을 때입니다. 여전히 UDF를 사용하는 것이 가장 좋은 방법이라고 생각합니다. 다른 제안이 있습니까?


답변

먼저 함수를 작성하십시오 (CTE를 사용하면 공통 테이블 표현식은 임시 테이블이 필요하지 않습니다)

 create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

그런 다음이 테이블을 테이블로 사용하거나 기존 저장 프로 시저에 맞게 수정하십시오.

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

최신 정보

4000 자보다 긴 입력 문자열에 대해서는 이전 버전이 실패합니다. 이 버전은 다음과 같은 제한 사항을 처리합니다.

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS s
from tokens
);

GO

사용법은 동일하게 유지됩니다.


답변

여기에서 대부분의 솔루션은 while 루프 또는 재귀 적 CTE를 사용합니다. 공백 이외의 구분 기호를 사용할 수 있다면 세트 기반 접근 방식이 우수 할 것입니다.

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM 
          ( 
            SELECT n = Number, 
              [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
              CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
            FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
              FROM sys.all_objects) AS x
              WHERE Number <= LEN(@List)
              AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
          ) AS y
        );

샘플 사용법 :

SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
  WHERE idx = 3;

결과 :

----
blat

idx함수에 인수로 원하는 것을 추가 할 수도 있지만 독자에게 연습으로 남겨 두겠습니다.

당신이 할 수없는 단지 기본 STRING_SPLIT기능 출력이 원래 목록의 순서로 렌더링 할 것이라는 보장이 없기 때문에, SQL 서버 2016에 추가했다. 즉, 3,6,1결과 를 전달 하면 순서 같을 있지만 그럴 있습니다 1,3,6. 내장 기능을 향상시키는 데 도움을 요청한 커뮤니티는 다음과 같습니다.

충분한 정 성적 피드백을 통해 실제로 다음과 같은 개선을 고려할 수 있습니다.

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

위의 SQL 서버 2016에,하지만 당신은 보라 STRING_SPLIT()STRING_AGG():


답변

숫자 테이블을 활용하여 문자열 구문 분석을 수행 할 수 있습니다.

물리 숫자 테이블을 만듭니다.

    create table dbo.Numbers (N int primary key);
    insert into dbo.Numbers
        select top 1000 row_number() over(order by number) from master..spt_values
    go

1000000 개의 행으로 테스트 테이블 만들기

    create table #yak (i int identity(1,1) primary key, array varchar(50))

    insert into #yak(array)
        select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
    go

함수 만들기

    create function [dbo].[ufn_ParseArray]
        (   @Input      nvarchar(4000), 
            @Delimiter  char(1) = ',',
            @BaseIdent  int
        )
    returns table as
    return  
        (   select  row_number() over (order by n asc) + (@BaseIdent - 1) [i],
                    substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
            from    dbo.Numbers
            where   n <= convert(int, len(@Input)) and
                    substring(@Delimiter + @Input, n, 1) = @Delimiter
        )
    go

사용법 (노트북에서 40 대 3mil 행 출력)

    select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)

대청소

    drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]

여기서 성능은 놀랍지 않지만 백만 행 테이블 이상의 함수를 호출하는 것이 가장 좋은 아이디어는 아닙니다. 문자열을 여러 행으로 나누면 함수를 피할 수 있습니다.


답변

이 질문은 문자열 분할 방식 이 아니라 n 번째 요소를 얻는 방법에 관한 것 입니다.

여기에 모든 답이 재귀를 사용하여 문자열을 분할 어떤 종류의 일을하는, CTE이야, 배수 CHARINDEX, REVERSE그리고 PATINDEX, 발명 기능은 CLR 방법, 번호 테이블에 대한 호출 CROSS APPLY로 … 대부분의 답변은 많은 코드를 다룹니다.

그러나 -n 번째 요소를 얻는 접근법 이상 을 정말로 원한다면 -이것은 실제로 하나의 라이너로 , UDF는없고, 하위 선택조차도 할 수 없으며 … 추가 혜택 : type safe

공백으로 구분 된 2 부 얻기 :

DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')

물론 구분자 및 위치에 변수사용할 수 있습니다 ( sql:column쿼리 값에서 위치를 직접 검색하는 데 사용 ).

DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')

문자열에 금지 된 문자 (특히 중 하나 &><) 가 포함될 수 있으면 여전히 이런 식으로 수행 할 수 있습니다. FOR XML PATH문자열에서 먼저 사용 하여 모든 금지 된 문자를 피팅 이스케이프 시퀀스로 암시 적으로 바꿉니다.

추가로 구분자가 세미콜론 인 경우 매우 특별한 경우 입니다. 이 경우 구분 기호를 먼저 ‘# DLMT #’로 바꾸고 마지막으로 XML 태그로 바꿉니다.

SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');

SQL-Server 2016 이상 업데이트

유감스럽게도 개발자는로 부품 색인을 반환하는 것을 잊었습니다 STRING_SPLIT. 그러나 SQL-Server 2016+를 사용하면 JSON_VALUE및이 OPENJSON있습니다.

함께 JSON_VALUE우리는 인덱스 ‘배열과 위치에 전달할 수있다.

대한 문서가 명확하게 진술한다 :OPENJSON

OPENJSON이 JSON 배열을 구문 분석 할 때이 함수는 JSON 텍스트의 요소 색인을 키로 리턴합니다.

같은 문자열은 1,2,3대괄호 만 필요합니다 : [1,2,3].
같은 단어의 문자열은 this is an example이어야 ["this","is","an","example"]합니다.
이것들은 매우 쉬운 문자열 연산입니다. 그냥 사용해보십시오.

DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;

--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));

-위치 안전 스트링 스플리터 ( 0부터 시작 )는 다음을 참조하십시오.

SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

에서 이 게시물에 나는 다양한 접근 방법을 테스트하고, 발견 OPENJSON정말 빠릅니다. 유명한 “delimitedSplit8k ()”메소드보다 훨씬 빠릅니다.

업데이트 2-유형 안전 값 가져 오기

doubled를 사용하여 배열 내에서 배열을 사용할 수 있습니다 [[]]. 이것은 유형이 지정된 WITH-clause를 허용합니다 .

DECLARE  @SomeDelimitedString VARCHAR(100)='part1|1|20190920';

DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');

SELECT @SomeDelimitedString          AS TheOriginal
      ,@JsonArray                    AS TransformedToJSON
      ,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment  VARCHAR(100) '$[0]'
    ,TheSecondFragment INT          '$[1]'
    ,TheThirdFragment  DATE         '$[2]') ValuesFromTheArray


답변

여기 UDF가 있습니다. 구분 된 값의 테이블을 반환하고 모든 시나리오를 시도하지는 않았지만 예제는 정상적으로 작동합니다.


CREATE FUNCTION SplitString
(
    -- Add the parameters for the function here
    @myString varchar(500),
    @deliminator varchar(10)
)
RETURNS
@ReturnTable TABLE
(
    -- Add the column definitions for the TABLE variable here
    [id] [int] IDENTITY(1,1) NOT NULL,
    [part] [varchar](50) NULL
)
AS
BEGIN
        Declare @iSpaces int
        Declare @part varchar(50)

        --initialize spaces
        Select @iSpaces = charindex(@deliminator,@myString,0)
        While @iSpaces > 0

        Begin
            Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

            Insert Into @ReturnTable(part)
            Select @part

    Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))


            Select @iSpaces = charindex(@deliminator,@myString,0)
        end

        If len(@myString) > 0
            Insert Into @ReturnTable
            Select @myString

    RETURN
END
GO

당신은 이것을 다음과 같이 부를 것입니다 :


Select * From SplitString('Hello John Smith',' ')

편집 : 다음과 같이 len> 1로 delimter를 처리하도록 솔루션이 업데이트되었습니다.


select * From SplitString('Hello**John**Smith','**')