String terpisah T-SQL


142

Saya memiliki kolom SQL Server 2008 R2 yang berisi string yang perlu saya pisahkan dengan koma. Saya telah melihat banyak jawaban di StackOverflow tetapi tidak ada yang berfungsi di R2. Saya telah memastikan bahwa saya memiliki izin pilih pada setiap contoh fungsi pemisahan. Setiap bantuan sangat dihargai.


7
Ini adalah salah satu dari sejuta jawaban yang saya suka stackoverflow.com/a/1846561/227755
nurettin

2
Apa maksud Anda "tidak ada yang berhasil"? Bisakah Anda lebih spesifik?
Aaron Bertrand

Andy memang mengarahkan saya ke arah yang benar karena saya menjalankan fungsi secara tidak benar. Inilah sebabnya mengapa tidak ada jawaban tumpukan lain yang berfungsi. Salahku.
Lee Grindon

2
kemungkinan duplikat string Split dalam SQL
Prahalad Gaggar

Ada mdq.RegexSplitfungsi di add-on "Master Data Services", yang dapat membantu. Tentu patut diselidiki .
jpaugh

Jawaban:


244

Saya telah menggunakan SQL ini sebelumnya yang mungkin berhasil untuk Anda: -

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

dan untuk menggunakannya: -

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')

2
Terima kasih banyak Andy. Saya membuat sedikit peningkatan pada skrip Anda untuk memungkinkan fungsi mengembalikan item pada indeks tertentu dalam string terpisah. Ini berguna hanya dalam situasi ketika Anda mengurai struktur kolom satu. gist.github.com/klimaye/8147193
CF_Maintainer

1
Saya memposting beberapa perbaikan (dengan kasus uji dukungan) ke halaman github saya di sini . Saya akan mempostingnya sebagai jawaban di utas Stack Overflow ini ketika saya memiliki cukup perwakilan untuk melebihi posting "perlindungan"
mpag

8
Meskipun ini adalah jawaban yang bagus, ini sudah ketinggalan zaman ... Pendekatan prosedural (terutama loop) adalah sesuatu yang harus dihindari ... Layak untuk melihat jawaban yang lebih baru ...
Shnugo

1
Ini tidak berhasil untukselect * from dbo.splitstring('')
Pasi Savolainen

2
Saya sangat setuju dengan @Shnugo. Pemisah perulangan bekerja tetapi sangat lambat. Sesuatu seperti sqlservercentral.com/articles/Tally+Table/72993 ini jauh lebih baik. Beberapa opsi berbasis set luar biasa lainnya dapat ditemukan di sini. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

63

Alih-alih CTE rekursif dan while loop, adakah yang mempertimbangkan pendekatan yang lebih berbasis set? Perhatikan bahwa fungsi ini ditulis untuk pertanyaan, yang didasarkan pada SQL Server 2008 dan koma sebagai pemisah . Di SQL Server 2016 dan di atasnya (dan di tingkat kompatibilitas 130 ke atas), STRING_SPLIT()adalah opsi yang lebih baik .

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE
AS
  RETURN ( SELECT [Value] FROM 
  ( 
    SELECT [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
      CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
    FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
      FROM sys.all_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

Jika Anda ingin menghindari batasan panjang string menjadi <= jumlah baris dalam sys.all_columns(9.980 di modeldalam SQL Server 2017; jauh lebih tinggi di database pengguna Anda sendiri), Anda dapat menggunakan pendekatan lain untuk mendapatkan angka, seperti membuat tabel angka Anda sendiri . Anda juga dapat menggunakan CTE rekursif jika Anda tidak dapat menggunakan tabel sistem atau membuatnya sendiri:

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

Tetapi Anda harus menambahkan OPTION (MAXRECURSION 0)(atau MAXRECURSION <longest possible string length if < 32768>) ke kueri luar untuk menghindari kesalahan dengan rekursi untuk string> 100 karakter. Jika itu juga bukan alternatif yang baik, lihat jawaban ini seperti yang ditunjukkan di komentar.

(Selain itu, pembatas haruslah NCHAR(<=1228). Masih meneliti alasannya.)

Lebih lanjut tentang fungsi split, mengapa (dan membuktikan bahwa) sementara loop dan CTE rekursif tidak diskalakan, dan alternatif yang lebih baik, jika string pemisahan berasal dari lapisan aplikasi:


1
Ada bug kecil dalam prosedur ini untuk kasus di mana akan ada nilai null di akhir string - seperti di '1,2,, 4,' - karena nilai akhir tidak diurai. Untuk memperbaiki bug ini, ekspresi "WHERE Number <= LEN (@List)" harus diganti dengan "WHERE Number <= LEN (@List) + 1".
SylvainL

@SylvainL Saya rasa itu tergantung pada perilaku apa yang Anda inginkan. Menurut pengalaman saya, kebanyakan orang ingin mengabaikan tanda koma karena mereka tidak benar-benar mewakili elemen nyata (berapa banyak salinan string kosong yang Anda butuhkan)? Bagaimanapun, cara sebenarnya untuk melakukan ini - jika Anda akan mengikuti tautan kedua - adalah melangkah bermain-main dengan memisahkan string jelek besar di T-SQL yang lambat.
Aaron Bertrand

1
Seperti yang Anda katakan, kebanyakan orang ingin mengabaikan tanda koma apa pun, tetapi sayangnya, tidak semua. Saya kira solusi yang lebih lengkap adalah menambahkan parameter untuk menentukan apa yang harus dilakukan dalam kasus ini, tetapi komentar saya hanyalah catatan kecil untuk memastikan bahwa tidak ada yang melupakan kemungkinan ini, karena ini bisa sangat nyata dalam banyak kasus.
SylvainL

Saya memiliki perilaku aneh dengan fungsi itu. Jika saya menggunakan string secara langsung sebagai parameter - itu berhasil. Jika saya memiliki varchar, itu tidak. Anda dapat mereproduksi dengan mudah: deklarasikan invarchar sebagai varchar set invarchar = 'ta; aa; qq' SELECT Value from [dbo]. [SplitString] (invarchar, ';') SELECT Value from [dbo]. [SplitString] ('ta; aa; qq ','; ')
Patrick Desjardins

Saya suka pendekatan ini, tetapi jika jumlah objek yang dikembalikan sys.all_objectskurang dari jumlah karakter dalam string input maka itu akan memotong string dan nilainya akan hilang. Karena sys.all_objectshanya digunakan sebagai sedikit retasan untuk menghasilkan baris, maka ada cara yang lebih baik untuk melakukan ini, misalnya jawaban ini .
buku jari

55

Akhirnya penantian selesai di SQL Server 2016 mereka telah memperkenalkan fungsi string terpisah:STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

Semua metode lain untuk membagi string seperti XML, tabel Tally, while loop, dll .. telah terpesona oleh STRING_SPLITfungsi ini .

Berikut adalah artikel luar biasa dengan perbandingan kinerja: Kejutan dan Asumsi Kinerja: STRING_SPLIT


5
jelas menjawab pertanyaan tentang bagaimana membagi string untuk mereka yang memiliki server yang diperbarui, tetapi bagi kita yang masih terjebak pada 2008 / 2008R2, harus menggunakan salah satu jawaban lain di sini.
mpag

2
Anda perlu melihat tingkat kompatibilitas dalam database Anda. Jika lebih rendah dari 130 Anda tidak akan dapat menggunakan fungsi STRING_SPLIT.
Luis Teijon

Sebenarnya, jika kompatibilitasnya bukan 130 dan Anda menjalankan 2016 (atau Azure SQL), Anda dapat mengatur kompatibilitas hingga 130 menggunakan: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Michieal

22

Cara termudah untuk melakukannya adalah dengan menggunakan XMLformat.

1. Mengubah string menjadi baris tanpa tabel

PERTANYAAN

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

HASIL

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. Mengonversi ke baris dari tabel yang memiliki ID untuk setiap baris CSV

TABEL SUMBER

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

PERTANYAAN

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

HASIL

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x

Pendekatan ini akan rusak jika @Stringmengandung karakter terlarang ... Saya baru saja memposting jawaban untuk mengatasi masalah ini.
Shnugo

9

Saya membutuhkan cara cepat untuk menyingkirkan +4dari kode pos .

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

Tidak ada proses ... tidak ada UDF ... hanya satu perintah inline kecil yang ketat yang melakukan apa yang harus dilakukan. Tidak mewah, tidak elegan.

Ubah pembatas sesuai kebutuhan, dll, dan itu akan berfungsi untuk apa pun.


5
Ini bukanlah tentang pertanyaannya. OP memiliki nilai seperti '234.542,23' dan mereka ingin membaginya menjadi tiga baris ... Baris ke-1: 234, baris ke-2: 542, baris ke-3: 23. Ini adalah hal yang sulit dilakukan di SQL.
codeulike

7

jika Anda mengganti

WHILE CHARINDEX(',', @stringToSplit) > 0

dengan

WHILE LEN(@stringToSplit) > 0

Anda dapat menghilangkan sisipan terakhir setelah loop sementara!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END

Ini akan mengakibatkan karakter terakhir dari elemen terakhir terpotong. yaitu "AL, AL" akan menjadi "AL" | "A" yaitu "ABC, ABC, ABC" akan menjadi "ABC" | "ABC" | "AB"
Pengembang Microsoft

menambahkan +1ke SELECT @pos = LEN(@stringToSplit)tampaknya mengatasi masalah itu. Namun, SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)akan kembali Invalid length parameter passed to the LEFT or SUBSTRING functionkecuali Anda menambahkan +1parameter ketiga dari SUBSTRING juga. atau Anda dapat mengganti tugas itu denganSET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag

1
Saya memposting beberapa perbaikan (dengan kasus uji dukungan) ke halaman github saya di sini . Saya akan mempostingnya sebagai jawaban di utas Stack Overflow ini ketika saya memiliki cukup perwakilan untuk melebihi posting "perlindungan"
mpag

Saya juga telah mencatat masalah yang ditunjukkan oleh Terry di atas. Namun logika yang diberikan oleh @AviG sangat keren sehingga tidak gagal di tengah-tengah daftar token yang panjang. Coba panggilan tes ini untuk memverifikasi (Panggilan ini harus mengembalikan 969 token) pilih * dari dbo.splitstring ('token1, token2 ,,,,,,,, token969') Kemudian saya mencoba kode yang diberikan oleh mpag untuk memeriksa hasil yang sama panggil di atas dan ternyata hanya dapat mengembalikan 365 token. Akhirnya saya memperbaiki kode oleh AviG di atas dan memposting fungsi bebas bug sebagai balasan baru di bawah karena komentar di sini hanya mengizinkan teks terbatas. Periksa balasan dengan nama saya untuk mencobanya.
Gemunu R Wickremasinghe

3

Semua fungsi untuk pemisahan string yang menggunakan beberapa jenis Perulangan (iterasi) memiliki kinerja yang buruk. Mereka harus diganti dengan solusi berbasis set.

Kode ini dijalankan dengan sangat baik.

CREATE FUNCTION dbo.SplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO

Pendekatan ini akan rusak jika @Listmengandung karakter terlarang ... Saya baru saja memposting jawaban untuk mengatasi masalah ini.
Shnugo

Saya meningkatkan tanggapan Anda karena tanggapan Anda bekerja dengan ruang sebagai pembatas dan yang terbanyak memilih tidak
KMC

3

Pendekatan yang sering digunakan dengan elemen XML putus jika ada karakter terlarang. Ini adalah pendekatan untuk menggunakan metode ini dengan semua jenis karakter, bahkan dengan titik koma sebagai pembatas.

Triknya adalah, pertama gunakan SELECT SomeString AS [*] FOR XML PATH('')untuk membuat semua karakter terlarang lolos dengan benar. Itulah alasan mengapa saya mengganti pembatas menjadi nilai ajaib untuk menghindari masalah dengan ;pembatas.

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

Hasil

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F

2

Saya harus menulis sesuatu seperti ini baru-baru ini. Inilah solusi yang saya dapatkan. Ini digeneralisasi untuk string pembatas apa pun dan saya pikir itu akan bekerja sedikit lebih baik:

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END

1

Solusi dengan menggunakan CTE, jika ada yang membutuhkannya (selain saya, yang jelas melakukannya, itulah mengapa saya menulisnya).

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit

1

Ini lebih sempit. Ketika saya melakukan ini, saya biasanya memiliki daftar ID unik yang dipisahkan koma (INT atau BIGINT), yang ingin saya gunakan sebagai tabel untuk digunakan sebagai gabungan dalam ke tabel lain yang memiliki kunci utama INT atau BIGINT. Saya ingin fungsi nilai tabel sebaris dikembalikan sehingga saya memiliki gabungan yang paling efisien.

Penggunaan sampel adalah:

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

Saya mencuri ide dari http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html , mengubahnya menjadi nilai tabel in-line dan berperan sebagai INT.

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO

1

Ada versi yang benar di sini tetapi saya pikir akan lebih baik untuk menambahkan sedikit toleransi kesalahan jika mereka memiliki tanda koma serta membuatnya sehingga Anda dapat menggunakannya bukan sebagai fungsi tetapi sebagai bagian dari kode yang lebih besar . Untuk berjaga-jaga jika Anda hanya menggunakannya sekali dan tidak memerlukan fungsi. Ini juga untuk integer (yang saya butuhkan untuk itu) jadi Anda mungkin harus mengubah tipe data Anda.

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs

jika Anda SET @StringToSeperate = @StringToSeperate+','langsung sebelum WHILEpengulangan, saya pikir Anda mungkin dapat menghilangkan blok "tambahkan nilai terakhir". Lihat juga sol'n saya di github
mpag

Berdasarkan jawaban manakah ini? Ada banyak jawaban di sini, dan agak membingungkan. Terima kasih.
jpaugh

1

Saya sedikit memodifikasi + fungsi Andy Robinson. Sekarang Anda hanya dapat memilih bagian yang diperlukan dari tabel pengembalian:

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5


1

Jika Anda memerlukan solusi ad-hoc cepat untuk kasus umum dengan kode minimum, CTE dua baris rekursif ini akan melakukannya:

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

Gunakan ini sebagai pernyataan yang berdiri sendiri atau cukup tambahkan CTE di atas ke salah satu kueri Anda dan Anda akan dapat menggabungkan tabel yang dihasilkan bdengan yang lain untuk digunakan dalam ekspresi lebih lanjut.

edit (oleh Shnugo)

Jika Anda menambahkan penghitung, Anda akan mendapatkan indeks posisi bersama dengan Daftar:

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

Hasil:

n   s
1   1
2   2333
3   344
4   4

Saya suka pendekatan ini. Saya harap Anda tidak keberatan, bahwa saya menambahkan beberapa peningkatan langsung ke jawaban Anda. Jangan ragu untuk mengedit ini dengan cara apa pun yang nyaman ...
Shnugo

1

Saya mengambil rute xml dengan membungkus nilai-nilai menjadi elemen (M tetapi ada yang berfungsi):

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)

0

berikut adalah versi yang dapat dibagi berdasarkan pola menggunakan patindex, adaptasi sederhana dari posting di atas. Saya punya kasus di mana saya perlu membagi string yang berisi beberapa karakter pemisah.


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

hasilnya terlihat seperti ini

stringa stringb x y z


0

Secara pribadi saya menggunakan fungsi ini:

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)

0

Saya telah mengembangkan Splitter ganda (Membawa dua karakter terpisah) seperti yang diminta di sini . Bisa menjadi beberapa nilai di utas ini karena paling banyak dirujuk untuk kueri yang berkaitan dengan pemisahan string.

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

Pemakaian:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

Kemungkinan Penggunaan (Dapatkan nilai kedua dari setiap pemisahan):

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2

0

Solusi berbasis cte rekursif

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;

0

Dengan segala hormat kepada @AviG, ini adalah versi bug gratis dari fungsi yang ia sediakan untuk mengembalikan semua token secara penuh.

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END

0

Ini berdasarkan jawaban Andy Robertson, saya membutuhkan pembatas selain koma.

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

Dan untuk menggunakannya:

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(Diuji di SQL Server 2008 R2)

EDIT: kode tes yang benar


-1
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END

-1

Anda dapat menggunakan fungsi ini:

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO

-1

Berikut adalah contoh yang bisa Anda gunakan sebagai fungsi atau Anda juga bisa meletakkan logika yang sama di dalam prosedur. --PILIH * dari [dbo] .fn_SplitString;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;

Pendekatan ini akan rusak jika @vCSVmengandung karakter terlarang ... Saya baru saja memposting jawaban untuk mengatasi masalah ini.
Shnugo

-1

/ *

Jawaban untuk string terpisah T-SQL
Berdasarkan jawaban dari Andy Robinson dan AviG
Fungsi yang ditingkatkan ref: Fungsi LEN tidak termasuk spasi tambahan di SQL Server
'File' ini harus valid sebagai file penurunan harga dan file SQL


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

Contoh uji: lihat URL yang direferensikan sebagai "fungsi yang ditingkatkan" di atas

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

* /


digulung kembali untuk menghormati "'File' ini harus valid sebagai file penurunan harga dan file SQL"
mpag

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.