Membagi fungsi yang setara dalam T-SQL?


128

Saya mencari untuk membagi '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ...' (dipisahkan koma) menjadi tabel atau variabel tabel .

Adakah yang memiliki fungsi yang mengembalikan masing-masing berturut-turut?



1
Erland Sommarskog telah mempertahankan jawaban otoritatif untuk pertanyaan ini selama 12 tahun terakhir: http://www.sommarskog.se/arrays-in-sql.html Tidak layak mereproduksi semua opsi di StackOverflow di sini, cukup kunjungi halamannya dan Anda akan belajar semua yang ingin Anda ketahui.
Portman

2
Baru-baru ini saya melakukan penelitian kecil yang membandingkan pendekatan yang paling umum untuk masalah ini, yang mungkin layak dibaca: sqlperformance.com/2012/07/t-sql-queries/split-strings dan sqlperformance.com/2012/08/t- sql-queries / ...
Aaron Bertrand

1
kemungkinan duplikat string Split dalam SQL
Luv

Sepertinya Anda punya beberapa jawaban yang bagus di sini; mengapa tidak menandai salah satunya sebagai jawaban atau uraikan masalah Anda secara lebih terperinci jika masih belum dijawab.
RyanfaeScotland

Jawaban:


51

Berikut ini adalah solusi kuno:

/*
    Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
    @sString nvarchar(2048),
    @cDelimiter nchar(1)
)
RETURNS @tParts TABLE ( part nvarchar(2048) )
AS
BEGIN
    if @sString is null return
    declare @iStart int,
            @iPos int
    if substring( @sString, 1, 1 ) = @cDelimiter 
    begin
        set @iStart = 2
        insert into @tParts
        values( null )
    end
    else 
        set @iStart = 1
    while 1=1
    begin
        set @iPos = charindex( @cDelimiter, @sString, @iStart )
        if @iPos = 0
            set @iPos = len( @sString )+1
        if @iPos - @iStart > 0          
            insert into @tParts
            values  ( substring( @sString, @iStart, @iPos-@iStart ))
        else
            insert into @tParts
            values( null )
        set @iStart = @iPos+1
        if @iStart > len( @sString ) 
            break
    end
    RETURN

END

Dalam SQL Server 2008 Anda dapat mencapai hal yang sama dengan kode .NET. Mungkin itu akan bekerja lebih cepat, tetapi yang pasti pendekatan ini lebih mudah dikelola.


Terima kasih, saya juga ingin tahu. Apakah ada kesalahan di sini? Saya menulis kode ini mungkin 6 tahun yang lalu dan sudah berfungsi sejak saat itu.
XOR

Saya setuju. Ini adalah solusi yang sangat bagus ketika Anda tidak ingin (atau tidak bisa) terlibat dengan membuat parameter tipe tabel, yang akan menjadi kasus dalam contoh saya. DBA telah mengunci fitur itu dan tidak akan mengizinkannya. Terima kasih XOR!
dscarr

DECLARE VarString NVARCHAR (2048) = 'Mike / John / Miko / Matt'; DECLARE CaracString NVARCHAR (1) = '/'; PILIH * DARI dbo.FnSplitString (VarString, CaracString)
fernando yevenes

55

Coba ini

DECLARE @xml xml, @str varchar(100), @delimiter varchar(10)
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET @delimiter = ','
SET @xml = cast(('<X>'+replace(@str, @delimiter, '</X><X>')+'</X>') as xml)
SELECT C.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as X(C)

ATAU

DECLARE @str varchar(100), @delimiter varchar(10)
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET @delimiter = ','
;WITH cte AS
(
    SELECT 0 a, 1 b
    UNION ALL
    SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter)
    FROM CTE
    WHERE b > a
)
SELECT SUBSTRING(@str, a,
CASE WHEN b > LEN(@delimiter) 
    THEN b - a - LEN(@delimiter) 
    ELSE LEN(@str) - a + 1 END) value      
FROM cte WHERE a > 0

Banyak lagi cara melakukan hal yang sama ada di sini. Bagaimana cara memisahkan string yang dibatasi koma?


9
Catatan untuk siapa saja yang mencari pembagi string umum: Solusi pertama yang diberikan di sini bukanlah pembagi string umum - ini aman hanya jika Anda yakin bahwa input tidak akan pernah mengandung <, >atau &(mis. Input adalah urutan bilangan bulat). Tiga karakter di atas akan menyebabkan Anda mendapatkan kesalahan parse alih-alih hasil yang diharapkan.
miroxlav

1
Acara dengan masalah yang disebutkan oleh miroxlav (Yang harus dipecahkan dengan beberapa pemikiran), ini pasti salah satu solusi paling kreatif yang saya temukan (Yang pertama)! Sangat bagus!
major-mann

SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter)Seharusnya garis itu benar SELECT b, CHARINDEX(@delimiter, @str, b+1) + LEN(@delimiter). The b + 1 membuat perbedaan besar. Diuji di sini dengan ruang sebagai pembatas, tidak berfungsi tanpa perbaikan ini.
JwJosefy

@miroxlav Juga, dalam pengalaman saya, menggunakan XML untuk membagi string adalah jalan memutar yang sangat mahal.
underscore_d

Solusi hebat! Perlu dicatat bahwa pengguna mungkin ingin menambahkan MAXRECURSIONopsi untuk membagi lebih dari 100 bagian, ganti LENdengan sesuatu dari stackoverflow.com/q/2025585 untuk menangani spasi, dan mengecualikan NULLbaris untuk NULLinput.
Kevinoid

27

Anda telah menandai SQL Server 2008 ini, tetapi pengunjung masa depan untuk pertanyaan ini (menggunakan SQL Server 2016+) mungkin ingin tahu STRING_SPLIT.

Dengan fungsi bawaan baru ini, Anda sekarang dapat menggunakannya

SELECT TRY_CAST(value AS INT)
FROM   STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',') 

Beberapa pembatasan fungsi ini dan beberapa hasil pengujian kinerja yang menjanjikan ada di posting blog ini oleh Aaron Bertrand .


13

Ini seperti .NET, bagi Anda yang terbiasa dengan fungsi itu:

CREATE FUNCTION dbo.[String.Split]
(
    @Text VARCHAR(MAX),
    @Delimiter VARCHAR(100),
    @Index INT
)
RETURNS VARCHAR(MAX)
AS BEGIN
    DECLARE @A TABLE (ID INT IDENTITY, V VARCHAR(MAX));
    DECLARE @R VARCHAR(MAX);
    WITH CTE AS
    (
    SELECT 0 A, 1 B
    UNION ALL
    SELECT B, CONVERT(INT,CHARINDEX(@Delimiter, @Text, B) + LEN(@Delimiter))
    FROM CTE
    WHERE B > A
    )
    INSERT @A(V)
    SELECT SUBSTRING(@Text,A,CASE WHEN B > LEN(@Delimiter) THEN B-A-LEN(@Delimiter) ELSE LEN(@Text) - A + 1 END) VALUE      
    FROM CTE WHERE A >0

    SELECT      @R
    =           V
    FROM        @A
    WHERE       ID = @Index + 1
    RETURN      @R
END

SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2'

9

di sini adalah fungsi pemisahan yang kamu tanyakan

CREATE FUNCTION [dbo].[split](
          @delimited NVARCHAR(MAX),
          @delimiter NVARCHAR(100)
        ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
        AS
        BEGIN
          DECLARE @xml XML
          SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'

          INSERT INTO @t(val)
          SELECT  r.value('.','varchar(MAX)') as item
          FROM  @xml.nodes('/t') as records(r)
          RETURN
        END

jalankan fungsi seperti ini

select * from dbo.split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',')

5
DECLARE
    @InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5'
    , @delimiter varchar(10) = ','

DECLARE @xml AS XML = CAST(('<X>'+REPLACE(@InputString,@delimiter ,'</X><X>')+'</X>') AS XML)
SELECT C.value('.', 'varchar(10)') AS value
FROM @xml.nodes('X') as X(C)

Sumber respons ini: http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited


Sementara ini secara teoritis dapat menjawab pertanyaan, akan lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini, dan menyediakan tautan untuk referensi.
Xavi López

1
@ Xavi: ok, saya sudah memasukkan bagian-bagian penting dari jawabannya. Terima kasih atas petunjuk Anda.
Mihai Bejenariu

3

Saya tergoda untuk menggunakan solusi favorit saya. Tabel yang dihasilkan akan terdiri dari 2 kolom: PosIdx untuk posisi integer yang ditemukan; dan Nilai dalam integer.


create function FnSplitToTableInt
(
    @param nvarchar(4000)
)
returns table as
return
    with Numbers(Number) as 
    (
        select 1 
        union all 
        select Number + 1 from Numbers where Number < 4000
    ),
    Found as
    (
        select 
            Number as PosIdx,
            convert(int, ltrim(rtrim(convert(nvarchar(4000), 
                substring(@param, Number, 
                charindex(N',' collate Latin1_General_BIN, 
                @param + N',', Number) - Number))))) as Value
        from   
            Numbers 
        where  
            Number <= len(@param)
        and substring(N',' + @param, Number, 1) = N',' collate Latin1_General_BIN
    )
    select 
        PosIdx, 
        case when isnumeric(Value) = 1 
            then convert(int, Value) 
            else convert(int, null) end as Value 
    from 
        Found

Ini bekerja dengan menggunakan CTE rekursif sebagai daftar posisi, dari 1 hingga 100 secara default. Jika Anda perlu menggunakan string yang lebih panjang dari 100, cukup panggil fungsi ini menggunakan 'option (maxrecursion 4000)' seperti berikut:


select * from FnSplitToTableInt
(
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0'
) 
option (maxrecursion 4000)

2
+1 untuk menyebutkan opsi maxrecursion. Jelas rekursi berat harus digunakan dengan hati-hati di lingkungan produksi, tetapi bagus untuk menggunakan CTE untuk melakukan impor data berat atau tugas konversi.
Tim Medora

3
CREATE FUNCTION Split
(
  @delimited nvarchar(max),
  @delimiter nvarchar(100)
) RETURNS @t TABLE
(
-- Id column can be commented out, not required for sql splitting string
  id int identity(1,1), -- I use this column for numbering splitted parts
  val nvarchar(max)
)
AS
BEGIN
  declare @xml xml
  set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'

  insert into @t(val)
  select
    r.value('.','varchar(max)') as item
  from @xml.nodes('//root/r') as records(r)

  RETURN
END
GO

pemakaian

Select * from dbo.Split(N'1,2,3,4,6',',')

3

CTE sederhana ini akan memberikan apa yang dibutuhkan:

DECLARE @csv varchar(max) = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15';
--append comma to the list for CTE to work correctly
SET @csv = @csv + ',';
--remove double commas (empty entries)
SET @csv = replace(@csv, ',,', ',');
WITH CteCsv AS (
    SELECT CHARINDEX(',', @csv) idx, SUBSTRING(@csv, 1, CHARINDEX(',', @csv) - 1) [Value]
    UNION ALL
    SELECT CHARINDEX(',', @csv, idx + 1), SUBSTRING(@csv, idx + 1, CHARINDEX(',', @csv, idx + 1) - idx - 1) FROM CteCsv
    WHERE CHARINDEX(',', @csv, idx + 1) > 0
)

SELECT [Value] FROM CteCsv

@ jinsungy Anda mungkin ingin melihat jawaban ini, ini lebih efisien daripada jawaban yang diterima dan lebih sederhana.
Michał Turczyn

2

Ini adalah versi lain yang benar-benar tidak memiliki batasan (misalnya: karakter khusus ketika menggunakan pendekatan xml, jumlah catatan dalam pendekatan CTE) dan ini berjalan jauh lebih cepat berdasarkan tes pada catatan 10M + dengan panjang rata-rata string sumber 4000. Semoga ini bisa membantu.

Create function [dbo].[udf_split] (
    @ListString nvarchar(max),
    @Delimiter  nvarchar(1000),
    @IncludeEmpty bit) 
Returns @ListTable TABLE (ID int, ListValue nvarchar(1000))
AS
BEGIN
    Declare @CurrentPosition int, @NextPosition int, @Item nvarchar(max), @ID int, @L int
    Select @ID = 1,
   @L = len(replace(@Delimiter,' ','^')),
            @ListString = @ListString + @Delimiter,
            @CurrentPosition = 1 
    Select @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
   While @NextPosition > 0 Begin
   Set  @Item = LTRIM(RTRIM(SUBSTRING(@ListString, @CurrentPosition, @NextPosition-@CurrentPosition)))
   If      @IncludeEmpty=1 or LEN(@Item)>0 Begin 
     Insert Into @ListTable (ID, ListValue) Values (@ID, @Item)
     Set @ID = @ID+1
   End
   Set  @CurrentPosition = @NextPosition+@L
   Set  @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
  End
    RETURN
END

1

Menggunakan tabel penghitungan di sini adalah salah satu fungsi string split (pendekatan terbaik) oleh Jeff Moden

CREATE FUNCTION [dbo].[DelimitedSplit8K]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
     -- enough to cover NVARCHAR(4000)
  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
;

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


0

Blog ini hadir dengan solusi yang cukup bagus menggunakan XML dalam T-SQL.

Ini adalah fungsi yang saya buat berdasarkan pada blog itu (ubah nama fungsi dan tipe hasil cast sesuai kebutuhan):

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[SplitIntoBigints]
(@List varchar(MAX), @Splitter char)
RETURNS TABLE 
AS
RETURN 
(
    WITH SplittedXML AS(
        SELECT CAST('<v>' + REPLACE(@List, @Splitter, '</v><v>') + '</v>' AS XML) AS Splitted
    )
    SELECT x.v.value('.', 'bigint') AS Value
    FROM SplittedXML
    CROSS APPLY Splitted.nodes('//v') x(v)
)
GO

0
CREATE Function [dbo].[CsvToInt] ( @Array varchar(4000)) 
returns @IntTable table 
(IntValue int)
AS
begin
declare @separator char(1)
set @separator = ','
declare @separator_position int 
declare @array_value varchar(4000) 

set @array = @array + ','

while patindex('%,%' , @array) <> 0 
begin

select @separator_position = patindex('%,%' , @array)
select @array_value = left(@array, @separator_position - 1)

Insert @IntTable
Values (Cast(@array_value as int))
select @array = stuff(@array, 1, @separator_position, '')
end

0
/* *Object:  UserDefinedFunction [dbo].[Split]    Script Date: 10/04/2013 18:18:38* */
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[Split]
(@List varchar(8000),@SplitOn Nvarchar(5))
RETURNS @RtnValue table
(Id int identity(1,1),Value nvarchar(100))
AS
BEGIN
    Set @List = Replace(@List,'''','')
    While (Charindex(@SplitOn,@List)>0)
    Begin

    Insert Into @RtnValue (value)
    Select
    Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))

    Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
    End

    Insert Into @RtnValue (Value)
    Select Value = ltrim(rtrim(@List))

    Return
END
go

Select *
From [Clv].[Split] ('1,2,3,3,3,3,',',')
GO

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.