쉼표로 열을 분할하는 T SQL 테이블 값 함수


10

Microsoft SQL Server 2008에서 테이블 값 함수를 작성하여 데이터베이스에서 쉼표로 구분 된 열을 사용하여 각 값에 대해 별도의 행을 뱉었습니다.

예 : "one, two, three, four"는 다음 값을 포함하는 열이 하나만있는 새 테이블을 반환합니다.

one
two
three
four

이 코드는 오류가 발생하기 쉽습니까? 내가 테스트 할 때

SELECT * FROM utvf_Split('one,two,three,four',',') 

그것은 영원히 실행되며 아무것도 반환하지 않습니다. MSSQL 서버 (WHY WHY WHY ?!)에 내장 된 분할 기능이 없으며 웹에서 찾은 모든 유사한 기능이 절대적으로 쓰레기이거나 단순히 내가하려고하는 것과 관련이 없기 때문에 이것은 특히 마음이 아프게됩니다. .

기능은 다음과 같습니다.

USE *myDBname*
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[utvf_SPlit] (@String VARCHAR(MAX), @delimiter CHAR)

RETURNS @SplitValues TABLE
(
    Asset_ID VARCHAR(MAX) NOT NULL
)

AS
BEGIN
            DECLARE @FoundIndex INT
            DECLARE @ReturnValue VARCHAR(MAX)

            SET @FoundIndex = CHARINDEX(@delimiter, @String)

            WHILE (@FoundIndex <> 0)
            BEGIN
                  DECLARE @NextFoundIndex INT
                  SET @NextFoundIndex = CHARINDEX(@delimiter, @String, @FoundIndex+1)
                  SET @ReturnValue = SUBSTRING(@String, @FoundIndex,@NextFoundIndex-@FoundIndex)
                  SET @FoundIndex = CHARINDEX(@delimiter, @String)
                  INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
            END

            RETURN
END

답변:


1

약간 재 작업했습니다 ...

DECLARE @FoundIndex INT
DECLARE @ReturnValue VARCHAR(MAX)

SET @FoundIndex = CHARINDEX(@delimiter, @String)

WHILE (@FoundIndex <> 0)
BEGIN
      SET @ReturnValue = SUBSTRING(@String, 0, @FoundIndex)
      INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
      SET @String = SUBSTRING(@String, @FoundIndex + 1, len(@String) - @FoundIndex)
      SET @FoundIndex = CHARINDEX(@delimiter, @String)
END

INSERT @SplitValues (Asset_ID) VALUES (@String)

RETURN

20

나는 이것을 루프로하지 않을 것이다. 훨씬 더 나은 대안이 있습니다. 분할 해야 할 때 가장 좋은 것은 CLR이며 Adam Machanic의 접근 방식은 내가 테스트 한 것 중 가장 빠릅니다 .

CLR을 구현할 수없는 경우 IMHO의 다음 가장 좋은 방법은 숫자 테이블입니다.

SET NOCOUNT ON;

DECLARE @UpperLimit INT = 1000000;

WITH n AS
(
    SELECT
        x = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM       sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    CROSS JOIN sys.all_objects AS s3
)
SELECT Number = x
  INTO dbo.Numbers
  FROM n
  WHERE x BETWEEN 1 AND @UpperLimit
  OPTION (MAXDOP 1); -- protecting from Paul White's observation

GO
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(Number) 
    --WITH (DATA_COMPRESSION = PAGE);
GO

...이 기능을 허용합니다 :

CREATE FUNCTION dbo.SplitStrings_Numbers
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN
   (
       SELECT Item = SUBSTRING(@List, Number, 
         CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)
       FROM dbo.Numbers
       WHERE Number <= CONVERT(INT, LEN(@List))
         AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
   );
GO

나는이 모든 기능이 작동 할 , 특히 다중 문 대신 인라인이기 때문에 작동 때보 다 더 잘 수행 할 것이라고 믿습니다 . 나는 왜 당신이 작동하지 않는지 조사하지 않았습니다. 왜냐하면 그 기능을 작동시키는 것이 가치가 있다고 생각하지 않기 때문입니다.

그러나 모든 것이 말했다 ...

SQL Server 2008을 사용하고 있기 때문에 처음부터 분리해야하는 이유가 있습니까? 차라리 TVP를 사용하고 싶습니다.

CREATE TYPE dbo.strings AS TABLE
(
  string NVARCHAR(4000)
);

이제이를 저장 프로 시저에 대한 매개 변수로 승인하고 TVF를 사용하는 것처럼 컨텐츠를 사용할 수 있습니다.

CREATE PROCEDURE dbo.foo
  @strings dbo.strings READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT Asset_ID = string FROM @strings;
  -- SELECT Asset_ID FROM dbo.utvf_split(@other_param, ',');
END

그리고 C # 등에서 직접 TVP를 DataTable로 전달할 수 있습니다. 특히 스토어드 프로 시저가 TVP를 호출하여 다시 분리 할 수 ​​있도록 앱에서 쉼표로 구분 된 문자열을 작성하는 경우 위의 솔루션보다 성능이 뛰어납니다. TVP에 대한 자세한 내용은 Erland Sommarskog의 위대한 기사를 참조하십시오 .

더 최근에는 문자열 분리에 대한 시리즈를 작성했습니다.

그리고 SQL Server 2016 이상 (또는 Azure SQL Database)을 사용하는 경우 여기에 블로그 기능있는 새로운 STRING_SPLIT기능이 있습니다.


6

SQL Server 2016에는 STRING_SPLIT () 함수가 도입되었습니다 . 여기에는 잘게 잘리는 끈과 구분 기호라는 두 가지 매개 변수가 있습니다. 출력은 리턴 된 값당 하나의 행입니다.

주어진 예를 들어

SELECT * FROM string_split('one,two,three,four', ',');

돌아올 것이다

value
------------------
one
two
three
four

1

Jeff Moden의 스트링 스플리터가 등장한 직후부터 사용하고 사랑해 왔습니다.

탈리 오! 향상된 SQL 8K“CSV 스플리터”기능

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  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
;

-2
CREATE FUNCTION [dbo].[fnSplit]
(

    @sInputList VARCHAR(8000),         -- List of delimited items

    @sDelimiter VARCHAR(8000) = ','    -- delimiter that separates items

)
RETURNS @List TABLE (colData VARCHAR(8000))

BEGIN

DECLARE @sItem VARCHAR(8000)

    WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0

    BEGIN

        SELECT @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX
(@sDelimiter,@sInputList,0)-1))),

        @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)
+LEN(@sDelimiter),LEN(@sInputList))))

        IF LEN(@sItem) > 0
            INSERT INTO @List SELECT @sItem
        END

        IF LEN(@sInputList) > 0
            INSERT INTO @List SELECT @sInputList -- Put the last item in
        RETURN
    END

--TEST

--Example 1: select * from fnSplit('1,22,333,444,,5555,666', ',')

--Example 2: select * from fnSplit('1##22#333##444','##')  --note second colData has embedded #

--Example 3: select * from fnSplit('1 22 333 444  5555 666', ' ')

여기에 이미지 설명을 입력하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.