Hàm SQL có giá trị để phân tách một cột trên dấu phẩy


10

Tôi đã viết một Hàm có giá trị bảng trong Microsoft SQL Server 2008 để lấy một cột được phân tách bằng dấu phẩy trong cơ sở dữ liệu để nhổ ra các hàng riêng biệt cho mỗi giá trị.

Ví dụ: "một, hai, ba, bốn" sẽ trả về một bảng mới chỉ có một cột chứa các giá trị sau:

one
two
three
four

Mã này có dễ bị lỗi không các bạn? Khi tôi kiểm tra nó với

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

nó chỉ chạy mãi mãi và không bao giờ trả lại bất cứ thứ gì Điều này thực sự gây thất vọng đặc biệt là vì không có chức năng phân tách tích hợp trên máy chủ MSSQL (TẠI SAO TẠI SAO?!) Và tất cả các chức năng tương tự tôi tìm thấy trên web là rác hoàn toàn hoặc đơn giản là không liên quan đến những gì tôi đang cố gắng làm .

Đây là chức năng:

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

Câu trả lời:


1

Làm việc lại một chút ...

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

Tôi sẽ không làm điều này với một vòng lặp; có nhiều lựa chọn thay thế tốt hơn. Cho đến nay, điều tốt nhất, khi bạn phải chia tách, là CLR và cách tiếp cận của Adam Machanic là cách nhanh nhất tôi đã thử nghiệm .

Cách tiếp cận tốt nhất tiếp theo IMHO, nếu bạn không thể thực hiện CLR, là bảng số:

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

... cho phép chức năng này:

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

Tôi tin rằng tất cả những thứ này sẽ hoạt động tốt hơn chức năng bạn có, khi bạn làm cho nó hoạt động, đặc biệt vì chúng là nội tuyến thay vì đa câu lệnh. Tôi đã không điều tra lý do tại sao của bạn không hoạt động, bởi vì tôi không nghĩ rằng nó đáng để làm cho chức năng đó hoạt động.

Nhưng tất cả đã nói ...

Vì bạn đang sử dụng SQL Server 2008, có lý do nào khiến bạn cần phải phân chia ngay từ đầu không? Tôi thà sử dụng TVP cho việc này:

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

Bây giờ bạn có thể chấp nhận điều này như một tham số cho các thủ tục được lưu trữ của mình và sử dụng nội dung giống như bạn sẽ sử dụng 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

Và bạn có thể truyền TVP trực tiếp từ C #, v.v. dưới dạng DataTable. Điều này gần như chắc chắn sẽ vượt trội hơn bất kỳ giải pháp nào ở trên, đặc biệt nếu bạn đang xây dựng một chuỗi được phân tách bằng dấu phẩy trong ứng dụng của mình để quy trình được lưu trữ của bạn có thể gọi TVP để phân tách lại. Để biết thêm thông tin về TVP, hãy xem bài viết tuyệt vời của Erland Sommarskog .

Gần đây, tôi đã viết một loạt bài về việc tách chuỗi:

Và nếu bạn đang sử dụng SQL Server 2016 hoặc mới hơn (hoặc Cơ sở dữ liệu SQL Azure), có một STRING_SPLITchức năng mới , mà tôi đã viết blog ở đây:


6

SQL Server 2016 đã giới thiệu hàm STRINGinksLIT () . Nó có hai tham số - chuỗi được cắt nhỏ và dấu phân cách. Đầu ra là một hàng cho mỗi giá trị được trả về.

Ví dụ đã cho

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

sẽ trở lại

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

1

Tôi đã sử dụng và yêu thích bộ chia chuỗi của Jeff Moden kể từ khi nó ra mắt.

Kiểm đếm OH! Một chức năng Splitter Splitter SQL 8K được cải tiến

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', ' ')

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.