T SQL Table Dinilai Berfungsi untuk Membagi Kolom pada koma

10

Saya menulis sebuah Table Valued Function di Microsoft SQL Server 2008 untuk mengambil kolom yang dibatasi koma dalam database untuk mengeluarkan baris terpisah untuk setiap nilai.

Mis: "satu, dua, tiga, empat" akan mengembalikan tabel baru dengan hanya satu kolom yang berisi nilai-nilai berikut:

one
two
three
four

Apakah kode ini terlihat rentan terhadap kalian? Ketika saya mengujinya

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

itu hanya berjalan selamanya dan tidak pernah mengembalikan apa pun. Ini menjadi sangat mengecewakan terutama karena tidak ada fungsi split dibangun di server MSSQL (MENGAPA MENGAPA MENGAPA ?!) dan semua fungsi serupa yang saya temukan di web adalah sampah mutlak atau hanya tidak relevan dengan apa yang saya coba lakukan .

Inilah fungsinya:

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
OvetS
sumber

Jawaban:

1

Bekerja ulang sedikit ...

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
Derek Kromm
sumber
20

Saya tidak akan melakukan ini dengan loop; ada banyak alternatif yang lebih baik. Sejauh ini yang terbaik, ketika Anda harus berpisah, adalah CLR, dan pendekatan Adam Machanic adalah yang tercepat yang pernah saya uji .

IMHO pendekatan terbaik berikutnya, jika Anda tidak dapat mengimplementasikan CLR, adalah tabel angka:

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

... yang memungkinkan fungsi ini:

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

Saya percaya semua ini akan berkinerja lebih baik daripada fungsi yang Anda miliki, ketika Anda membuatnya berfungsi, terutama karena mereka sebaris bukan multi-pernyataan. Saya belum menyelidiki mengapa fungsi Anda tidak berfungsi, karena menurut saya tidak ada gunanya menjalankan fungsi itu.

Tapi itu semua mengatakan ...

Karena Anda menggunakan SQL Server 2008, adakah alasan Anda perlu membagi di tempat pertama? Saya lebih suka menggunakan TVP untuk ini:

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

Sekarang Anda dapat menerima ini sebagai parameter untuk prosedur tersimpan Anda, dan menggunakan konten seperti Anda menggunakan 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

Dan Anda dapat melewati TVP langsung dari C # dll sebagai DataTable. Ini hampir pasti akan mengungguli salah satu solusi di atas, terutama jika Anda membuat string yang dipisahkan koma di aplikasi Anda secara khusus sehingga prosedur tersimpan Anda dapat memanggil TVP untuk membaginya lagi. Untuk info lebih lanjut tentang TVP, lihat artikel hebat Erland Sommarskog .

Baru-baru ini, saya telah menulis seri tentang string yang memisahkan:

Dan jika Anda menggunakan SQL Server 2016 atau yang lebih baru (atau Azure SQL Database), ada fungsi baruSTRING_SPLIT , yang saya blogging di sini:

Aaron Bertrand
sumber
6

SQL Server 2016 memperkenalkan fungsi STRING_SPLIT () . Ini memiliki dua parameter - string yang akan dicincang dan pemisah. Outputnya adalah satu baris per nilai yang dikembalikan.

Untuk contoh yang diberikan

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

akan kembali

value
------------------
one
two
three
four
Michael Green
sumber
1

Saya telah menggunakan dan mencintai string splitter Jeff Moden sejak itu keluar.

Tally OH! Fungsi SQL 8K “CSV Splitter” yang Ditingkatkan

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
;
Erik Darling
sumber
-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', ' ')

masukkan deskripsi gambar di sini

Mudassir
sumber