T-SQL bölünmüş dize


139

Virgülle bölmem gereken bir dize içeren bir SQL Server 2008 R2 sütunu var. StackOverflow üzerinde birçok cevap gördüm ama hiçbiri R2'de çalışmıyor. Herhangi bir split işlevi örnekleri üzerinde seçme izinleri var emin oldum. Herhangi bir yardım büyük takdir.


7
Bu sevdiğim milyon cevaptan biri stackoverflow.com/a/1846561/227755
nurettin

2
Ne demek "hiçbiri işe yaramaz"? Daha spesifik olabilir misiniz?
Aaron Bertrand

Fonksiyonu yanlış yerine getirirken Andy beni doğru yöne doğrulttu. Bu yüzden diğer yığın cevaplarının hiçbiri işe yaramadı. Benim hatam.
Lee Grindon


mdq.RegexSplit"Ana Veri Hizmetleri" eklentisinde yardımcı olabilecek bir işlev vardır . Kesinlikle araştırmaya değer .
jpaugh

Yanıtlar:


233

Daha önce sizin için çalışabilir bu SQL kullandım: -

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

ve kullanmak için: -

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

1
Güzel bir, bu tam olarak ne aradığını çok teşekkürler
Lee Grindon

2
Çok teşekkürler Andy. Ben işlev bölünmüş dize belirli bir dizinde bir öğe döndürmek için komut dosyası için küçük bir geliştirme yaptım. Yalnızca bir sütunun yapısının ayrıştırıldığı durumlarda yararlıdır. gist.github.com/klimaye/8147193
CF_Maintainer

1
Burada github sayfama bazı iyileştirmeler (destek test senaryoları ile) gönderdim . Ben post "koruma" aşmak için yeterli temsilcisi olduğunda bu Yığın Taşma iş parçacığında bir cevap olarak
göndereceğiz

8
Bu harika bir cevap olmasına rağmen, modası geçmiş ... Prosedürel yaklaşımlar (özellikle döngüler) kaçınılması gereken bir şey ... Daha yeni cevaplara bakmaya değer ...
Shnugo

2
@Shnugo'ya tamamen katılıyorum. Döngü ayırıcılar çalışır, ancak korkunç derecede yavaştır. Bunun gibi bir şey sqlservercentral.com/articles/Tally+Table/72993 çok daha iyi. Bazı mükemmel set tabanlı seçenekler burada bulunabilir. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

61

Özyinelemeli CTE'ler ve döngüler yerine, daha set tabanlı bir yaklaşım düşünen var mı? Bu işlevin, SQL Server 2008 ve virgül olarak ayırıcı olarak temel alınan soru için yazıldığını unutmayın . SQL Server 2016 ve üstü sürümlerde (ve 130 ve üstü uyumluluk düzeyinde) STRING_SPLIT()daha iyi bir seçenektir .

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

Dizenin uzunluğunun <= satır sayısı sınırlamasından kaçınmak istiyorsanız sys.all_columns( modelSQL Server 2017'de 9.980 inç ; kendi kullanıcı veritabanlarınızda çok daha yüksek), sayıları türetmek için diğer yaklaşımları kullanabilirsiniz. kendi sayı tablonuzu oluşturmak . Sistem tablolarını kullanamayacağınız veya kendinizinkini oluşturamadığınız durumlarda yinelemeli bir CTE de kullanabilirsiniz:

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

Ancak ,> 100 karakterden uzun dizeler için özyineleme hatalarını önlemek için dış sorguyu eklemeniz OPTION (MAXRECURSION 0)(veya MAXRECURSION <longest possible string length if < 32768>) eklemeniz gerekir . Bu da iyi bir alternatif değilse, bu cevabı yorumlarda belirtildiği gibi görün.

(Ayrıca, sınırlayıcının olması gerekir NCHAR(<=1228). Hala nedenini araştırıyor.)

Bölünmüş işlevler hakkında daha fazla bilgi için, döngüler ve özyinelemeli CTE'ler ölçeklenmezken neden (ve kanıtı) ve uygulama katmanından gelen bölme dizeleri varsa daha iyi alternatifler:


1
Bu yordamda, son değer ayrıştırılmadığından, dizenin sonunda - '1,2, 4,' gibi - boş bir değerin olacağı durumda küçük bir hata vardır. Bu hatayı düzeltmek için, "WHERE Number <= LEN (@List)" ifadesinin yerine "WHERE Number <= LEN (@List) + 1" yazılmalıdır.
SylvainL

@SylvainL Sanırım hangi davranış istediğinize bağlı. Deneyimlerime göre, çoğu insan gerçek bir öğeyi temsil etmediği için (herhangi bir boş dizenin kaç kopyasına ihtiyacınız var) herhangi bir virgül görmezden gelmek ister? Her neyse, bunu yapmanın gerçek yolu - ikinci bağlantıyı takip ederseniz - büyük çirkin dizeleri yavaş T-SQL'de bölmekle uğraşmaktır.
Aaron Bertrand

1
Söylediğiniz gibi, çoğu insan ne yazık ki, hepsi değil, sondaki virgülleri göz ardı etmek istiyor. Daha eksiksiz bir çözümün, bu durumda ne yapılacağını belirtmek için bir parametre eklemek olacağını düşünüyorum, ancak yorumum, kimsenin bu olasılığı unutmamasını sağlamak için küçük bir not, çünkü birçok durumda oldukça gerçek olabilir.
SylvainL

Bu işlevle garip bir davranışım var. Parametre olarak doğrudan bir dize kullanırsam çalışır. Bir varcharım varsa, yok. Kolayca çoğaltabilirsiniz: invarchar bildirimini varchar set invarchar = 'ta; aa; qq' SELECT Değeri [dbo] 'den. [SplitString] (invarchar,'; ') SELECT [dbo]' dan Değer. [SplitString] ('ta; aa; qq ','; ')
Patrick Desjardins

Bu yaklaşımı beğendim, ancak döndürülen nesnelerin sys.all_objectssayısı giriş dizesindeki karakter sayısından azsa, dizeyi kesecek ve değerler kaybolacaktır. Yana sys.all_objectssadece satırları oluşturmak için bir hack biraz olarak kullanılıyor, o zaman örneğin bunun için daha iyi yollar vardır bu cevabı .
boğumlar

56

Nihayet SQL Server 2016'da bekleme bitti, Split string fonksiyonunu tanıttılar:STRING_SPLIT

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

XML, Tally tablosu, while döngüsü, vb. Gibi dizeyi bölmek için diğer tüm yöntemler bu STRING_SPLITişlev tarafından uçurulmuştur .

İşte performans karşılaştırması ile mükemmel bir makale: Performans Sürprizleri ve Varsayımlar: STRING_SPLIT


5
Açıkçası, güncellenmiş sunucuları olanlar için dizeyi nasıl böleceğimize dair soruyu cevaplıyor, ancak 2008 / 2008R2'de hala sıkışmış olanlarımız, buradaki diğer cevaplardan biriyle gitmek zorunda kalacak.
mpag

2
Veritabanınızdaki uyumluluk düzeyine göz atmanız gerekir. 130'dan düşükse STRING_SPLIT işlevini kullanamazsınız.
Luis Teijon

Aslında, uyumluluk 130 değilse ve 2016 (veya Azure SQL) çalıştırıyorsanız, uyumluluğu kullanarak 130'a kadar ayarlayabilirsiniz: ALTER DATABASE VeritabanıAdı SET COMPATIBILITY_LEVEL = 130
Michieal 15:17

23

Bunu yapmanın en kolay yolu XMLbiçimi kullanmaktır .

1. tablo olmadan satırlara dize dönüştürme

SORGU

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)

SONUÇ

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

2. Her bir CSV satırı için kimliği olan bir tablodaki satırlara dönüştürme

KAYNAK TABLOSU

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

SORGU

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

SONUÇ

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

@StringYasak karakterler içeriyorsa bu yaklaşım kırılacaktır ... Bu sorunun üstesinden gelmek için bir cevap gönderdim .
Shnugo

9

+4Bir posta kodundan kurtulmak için hızlı bir yol gerekiyordu .

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

Proc yok ... UDF yok ... Yapması gerekeni yapan tek bir sıkı küçük satır içi komut. Süslü değil, zarif değil.

Sınırlayıcıyı gerektiği gibi değiştirin, vb. Her şey için işe yarayacaktır.


4
Sorunun konusu bu değil. OP'nin '234,542,23' gibi bir değeri vardır ve bunu üç satıra bölmek isterler ... 1. sıra: 234, 2. sıra: 542, 3. sıra: 23. SQL'de yapmak zor bir şey.
codeulike

7

eğer değiştirirsen

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

ile

WHILE LEN(@stringToSplit) > 0

while döngüsünden sonra bu son eklemeyi kaldırabilirsiniz!

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

Bu, son öğenin son karakterinin kesilmesine neden olur. yani "AL, AL" "AL" olur | "A" yani "ABC, ABC, ABC" "ABC" olur | "ABC" | "AB"
Microsoft Geliştirici

ekleniyor +1gibi SELECT @pos = LEN(@stringToSplit)görünüyorsa bu sorunu ele alıyor. Ancak SUBSTRING öğesinin üçüncü parametresine de eklemediğiniz sürece SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)geri döner . veya bu ödeviInvalid length parameter passed to the LEFT or SUBSTRING function+1SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag

1
Burada github sayfama bazı iyileştirmeler (destek test senaryoları ile) gönderdim . Ben post "koruma" aşmak için yeterli temsilcisi olduğunda bu Yığın Taşma iş parçacığında bir cevap olarak
göndereceğiz

Ben de yukarıda Terry'nin işaret ettiği konuyu not ettim. Ancak @AviG tarafından verilen mantık o kadar güzel ki, ortada uzun bir jeton listesi için başarısız olmaz. Doğrulamak için bu test çağrısını deneyin (Bu çağrı 969 jeton döndürmelidir) dbo.splitstring ('token1, token2 ,,,,,,,, token969') 'dan select *' i seçtikten sonra sonuçları kontrol etmek için mpag tarafından verilen kodu denedim Yukarıdaki numarayı arayın ve yalnızca 365 jeton döndürebildiğini tespit edin. Sonunda yukarıda AviG tarafından kod düzeltildi ve burada yorum sadece sınırlı metin izin verdiği için hata ücretsiz fonksiyonu aşağıda yeni bir yanıt olarak yayınlanmıştır. Denemek için adımın altındaki yanıtı kontrol edin.
Gemunu R Wickremasinghe

3

Bir tür Döngü (yineleme) kullanan dize bölme için tüm işlevlerin performansı kötüdür. Bunlar set bazlı çözelti ile değiştirilmelidir.

Bu kod mükemmel çalışır.

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

@ListYasak karakterler içeriyorsa bu yaklaşım kırılacaktır ... Bu sorunun üstesinden gelmek için bir cevap gönderdim .
Shnugo

Yanıtınızı destekliyorum çünkü sizinkiler sınırlayıcı olarak boşlukla çalışıyor ve en yüksek oyu alan biri çalışmıyor
KMC

3

XML öğeleriyle sık kullanılan yaklaşım, yasak karakterler durumunda kırılır. Bu, sınırlayıcı olarak noktalı virgül olsa bile, bu yöntemi her türlü karakterle kullanmak için bir yaklaşımdır.

İşin püf noktası, ilk olarak SELECT SomeString AS [*] FOR XML PATH('')tüm yasak karakterlerin düzgün bir şekilde kaçmasını sağlamak için kullanılır. Bu yüzden, sınırlayıcı olarak sorunlardan kaçınmak için sınırlayıcıyı sihirli bir değere değiştirmemin nedeni budur ;.

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)

Sonuç

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

2

Son zamanlarda böyle bir şey yazmak zorunda kaldım. İşte bulduğum çözüm. Herhangi bir sınırlayıcı dize için genelleştirilmiş ve biraz daha iyi performans olacağını düşünüyorum:

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

CTE kullanan bir çözüm, eğer herkes buna ihtiyaç duyarsa (benden başka, açıkça yapan, bu yüzden yazdım).

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

Bu daha dar bir şekilde uyarlanmıştır. Bunu yaptığımda genellikle INT veya BIGINT birincil anahtarına sahip başka bir tabloya iç birleşim olarak kullanmak için tablo olarak yayınlamak istediğim benzersiz kimliklerin virgülle ayrılmış bir listesi (INT veya BIGINT) var. Satır içi tablo değerli bir işlev döndürmek istiyorum böylece mümkün olan en verimli birleştirme var.

Örnek kullanım:

 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

Bu fikri http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html adresinden çalarak satır içi tablo değerli ve INT olarak yayınladım.

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

Burada doğru bir sürüm var, ancak bir virgül varsa ve bir işlev olarak değil, daha büyük bir kod parçasının parçası olarak kullanabilmeniz için küçük bir hata toleransı eklemenin iyi olacağını düşündüm. . Sadece bir kez kullanmanız ve bir işleve ihtiyacınız olmaması durumunda. Bu aynı zamanda tamsayılar için (bunun için ihtiyacım olan şey) bu yüzden veri türlerinizi değiştirmeniz gerekebilir.

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

Eğer döngüden SET @StringToSeperate = @StringToSeperate+','hemen önce WHILEolsaydınız "son değer ekle" bloğunu ortadan kaldırabileceğinizi düşünüyorum. Ayrıca bkz. Benim sol'n üzerinde github
mpag

Bu hangi cevaba dayanıyor? Burada birçok cevap var ve bu biraz kafa karıştırıcı. Teşekkürler.
jpaugh

1

+ Andy Robinson'ın işlevini biraz değiştirdim. Artık dönen tablodan yalnızca gerekli kısmı seçebilirsiniz:

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

Minimum kod içeren yaygın durumlar için hızlı ve geçici bir çözüme ihtiyacınız varsa, bu özyinelemeli CTE iki astarı bunu yapacaktır:

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

Bunu tek başına bir ifade olarak kullanın veya yukarıdaki CTE'leri sorgularınızdan herhangi birine ekleyin ve sonuçta ortaya çıkan tabloyu bbaşka ifadelerde kullanmak üzere başkalarıyla birleştirebileceksiniz .

edit (Shnugo tarafından)

Bir sayaç eklerseniz, Liste ile birlikte bir konum dizini alırsınız:

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;

Sonuç:

n   s
1   1
2   2333
3   344
4   4

Bu yaklaşımı seviyorum. Umarım umursamazsın, cevabına doğrudan bazı geliştirmeler ekledim. - Bunu herhangi bir şekilde düzenlemekten çekinmeyin ...
Shnugo

1

Ben değerleri elemanları (M ama her şey işe yarıyor) içine sararak xml yol almak:

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

burada patindex kullanarak bir desene ayrılabilen bir versiyon, yukarıdaki yazının basit bir uyarlaması. Birden fazla ayırıcı karakter içeren bir dizeyi bölmek için gereken bir durum vardı.


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','%[/,]%');

sonuç böyle görünüyor

stringa stringb x y z


0

Personnaly Bu işlevi kullanın:

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

Burada istendiği gibi bir çift Splitter (iki bölünmüş karakter alır) geliştirdik . Bu iş parçacığında, dize bölme ile ilgili sorgular için en çok başvuruda bulunulan bir değer olabilir.

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

Kullanımı:

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

Olası Kullanım (Her bölünmenin ikinci değerini alın):

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

Burada işlev olarak kullanabileceğiniz veya aynı mantığı işleme koyabileceğiniz bir örnek verilmiştir. - [dbo] .fn_SplitString öğesinden SEÇİN *;

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;

@vCSVYasak karakterler içeriyorsa bu yaklaşım kırılacaktır ... Bu sorunun üstesinden gelmek için bir cevap gönderdim .
Shnugo

0

Özyinelemeli kte tabanlı çözüm

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

Bu Andy Robertson'ın cevabına dayanıyor, virgül dışında bir sınırlayıcıya ihtiyacım vardı.

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

Ve kullanmak için:

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

(SQL Server 2008 R2 üzerinde test edilmiştir)

EDIT: doğru test kodu


0

/ *

Cevap T-SQL bölme dizesinde
gelen yanıtlara dayanarak Andy Robinson ve AviG
işlevselliği ref Geliştirilmiş: SQL Server sondaki boşlukları hariç LEN fonksiyonu
Bu 'dosyasında' bir markdown dosyası ve bir SQL dosyası hem de geçerli olmalıdır


*/

    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

/*

Test örnekleri: yukarıdaki "gelişmiş işlevsellik" olarak adlandırılan URL'ye bakın

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

* /


"Bu" dosya "hem bir işaretleme dosyası hem de bir SQL dosyası olarak geçerli olmalıdır" için geri
alındı

-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

Bu işlevi kullanabilirsiniz:

        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

@AviG'e gereken tüm saygıyla, bu, tüm jetonları tam olarak geri döndürmek için adadığı işlevin hatasız sürümüdür.

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

-3

En kolay yol:

  1. SQL Server 2016'yı yükleyin
  2. STRING_SPLIT kullanın https://msdn.microsoft.com/en-us/library/mt684588.aspx

Ekspres baskıda bile çalışır :).


"Uyumluluk düzeyini" SQL Server 2016 (130) olarak ayarlamayı unutmayın - yönetim stüdyosunda, veritabanına sağ tıklayın, özellikler / seçenekler / uyumluluk seviyesi.
Tomino

1
Orijinal yazı SQL 2008 R2 için söyledi. SQL 2016'yı yüklemek bir seçenek olmayabilir
Shawn Gavett
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.