SQL Server'ı kullanarak, x öğesine erişebilmek için bir dizeyi nasıl bölebilirim?
"Merhaba John Smith" dizesini al. Dizeyi boşluğa nasıl bölerim ve dizin 1'deki "John" döndürmesi gereken öğeye nasıl erişebilirim?
SQL Server'ı kullanarak, x öğesine erişebilmek için bir dizeyi nasıl bölebilirim?
"Merhaba John Smith" dizesini al. Dizeyi boşluğa nasıl bölerim ve dizin 1'deki "John" döndürmesi gereken öğeye nasıl erişebilirim?
Yanıtlar:
Sınırlandırılmış bir dizeyi ayrıştırmak için SQL Kullanıcı Tanımlı İşlevinde ( Kod Projesi'nden ) çözümü bulabilirsiniz .
Bu basit mantığı kullanabilirsiniz:
Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null
WHILE LEN(@products) > 0
BEGIN
IF PATINDEX('%|%', @products) > 0
BEGIN
SET @individual = SUBSTRING(@products,
0,
PATINDEX('%|%', @products))
SELECT @individual
SET @products = SUBSTRING(@products,
LEN(@individual + '|') + 1,
LEN(@products))
END
ELSE
BEGIN
SET @individual = @products
SET @products = NULL
SELECT @individual
END
END
SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))
olmasın SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
dizeyi ayıracak ve bir sütunda kullanabileceğiniz tek sütunlu bir tablo sonucu döndürecek yerleşik bir işlev var.SELECT
deyimde veya başka yerde .
SQL Server yerleşik bir bölünmüş işlevi olduğuna inanmıyorum, bu yüzden bir UDF dışında, bildiğim tek cevap PARSENAME işlevini ele geçirmektir:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME bir dize alır ve nokta karakterine böler. Bir sayıyı ikinci bağımsız değişkeni olarak alır ve bu sayı dizenin hangi segmentinin döndürüleceğini belirtir (arkadan öne doğru).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Açık sorun, dizenin zaten bir nokta içermesidir. Hala bir UDF kullanmanın en iyi yol olduğunu düşünüyorum ... başka herhangi bir öneriniz var mı?
SPLIT()
işlev sağlanmaz çünkü veritabanı tasarımı zayıftır ve veritabanı hiçbir zaman bu biçimde saklanan verileri kullanacak şekilde optimize edilmez. RDBMS, geliştiricilerin ele almaması için tasarlanan aptalca şeyler yapmalarına yardımcı olmakla yükümlü değildir . Doğru cevap her zaman "Veritabanınızı size 40 yıl önce söylediğimiz gibi normalleştirin" olacaktır . Ne SQL ne de RDBMS kötü tasarım için sorumlu değildir.
İlk olarak, bir işlev oluşturun (CTE kullanarak ortak tablo ifadesi geçici tabloya olan ihtiyacı ortadan kaldırır)
create function dbo.SplitString
(
@str nvarchar(4000),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
1,
1,
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 zeroBasedOccurance,
substring(
@str,
a,
case when b > 0 then b-a ELSE 4000 end)
AS s
from tokens
)
GO
Ardından, bunu herhangi bir tablo olarak kullanın (veya varolan depolanmış proc'unuza sığacak şekilde değiştirin).
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Güncelleme
4000 karakterden uzun giriş dizesi için önceki sürüm başarısız olur. Bu sürüm sınırlamaya dikkat eder:
create function dbo.SplitString
(
@str nvarchar(max),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
cast(1 as bigint),
cast(1 as bigint),
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 ItemIndex,
substring(
@str,
a,
case when b > 0 then b-a ELSE LEN(@str) end)
AS s
from tokens
);
GO
Kullanım aynı kalır.
100
(sonsuz döngüyü önlemek için). Kullanım MAXRECURSION ipucu yineleme seviyesi sayısı (tanımlamak 0
için 32767
, 0
"herhangi bir sınırlama" bir - server ezilme olabilir). BTW, çok daha iyi bir cevap PARSENAME
, çünkü evrensel :-). +1
maxrecursion
bu çözüm akılda tutmak bu soru ve cevaplara Nasıl kurulur maxrecursion
bir Tablo Değerli-Function içinde bir CTE seçenek .
s
artık tanımlanmadı
Buradaki çözümlerin çoğu döngüler veya özyinelemeli CTE'ler kullanırken kullanılır. Alan tabanlı bir sınırlayıcı kullanabiliyorsanız, set tabanlı bir yaklaşım daha üstün olacaktır, söz veriyorum:
CREATE FUNCTION [dbo].[SplitString]
(
@List NVARCHAR(MAX),
@Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM
(
SELECT n = Number,
[Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(@List)
AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
) AS y
);
Örnek kullanım:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Sonuçlar:
----
blat
Ayrıca idx
, işlevi bağımsız değişken olarak istediğinizi ekleyebilirsiniz , ancak bunu okuyucuya bir alıştırma olarak bırakacağım.
Sen ile bunu yapamaz sadece yerli STRING_SPLIT
fonksiyonu çıkışı orijinal liste sırasına göre icra edileceğini garantisi yoktur, çünkü SQL Server 2016 yılında ekledi. Başka bir deyişle, başarılı olursa 3,6,1
muhtemelen bu sırayla olacak sonuç, ancak olabilir olmak 1,3,6
. Topluluğun yerleşik işlevi iyileştirme konusunda yardım istedim:
Yeterli niteliksel geribildirimle, aslında bu geliştirmelerden bazılarını yapmayı düşünebilirler:
Bölünmüş işlevler hakkında daha fazla bilgi için, döngüler ve özyinelemeli CTE'ler ölçeklenmezken neden (ve bunun kanıtı) ve uygulama katmanından gelen bölme dizeleri geliyorsa daha iyi alternatifler:
Yukarıdaki SQL Server 2016 veya üzerinde olsa da, size bakmak gerekir STRING_SPLIT()
ve STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
ve üretilen çıktı: Değer Merhaba ello llo lo o John ohn hn n smith mith th h
Dize ayrıştırma işlemi yapmak için bir Sayı tablosundan yararlanabilirsiniz.
Fiziksel sayılar tablosu oluşturun:
create table dbo.Numbers (N int primary key);
insert into dbo.Numbers
select top 1000 row_number() over(order by number) from master..spt_values
go
1000000 satırlı test tablosu oluşturma
create table #yak (i int identity(1,1) primary key, array varchar(50))
insert into #yak(array)
select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
go
İşlevi oluştur
create function [dbo].[ufn_ParseArray]
( @Input nvarchar(4000),
@Delimiter char(1) = ',',
@BaseIdent int
)
returns table as
return
( select row_number() over (order by n asc) + (@BaseIdent - 1) [i],
substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
from dbo.Numbers
where n <= convert(int, len(@Input)) and
substring(@Delimiter + @Input, n, 1) = @Delimiter
)
go
Kullanım (dizüstü bilgisayarımda 40'larda 3mil satır çıktı)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
Temizlemek
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
Buradaki performans şaşırtıcı değil, ancak bir milyon satırlık tablodan fazla bir işlevi çağırmak en iyi fikir değil. Birçok dizeye bölünmüş bir dize gerçekleştirirseniz, işlevi önlemek.
desc
Kaldırılırsa daha sezgisel sonuçlar vermez mi ?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
içinde tamamlandı. @hello_earth Çözümünüz 4'ten fazla alana sahip daha uzun dizelerle nasıl karşılaştırılır?
Bu soru dize bölünmüş bir yaklaşımla değil , n'inci öğenin nasıl alınacağıyla ilgilidir .
Buradaki tüm cevaplar özyineleme kullanarak dize bölünme çeşit yapıyoruz, CTE
s, multipl CHARINDEX
, REVERSE
ve PATINDEX
, icat fonksiyonlar, CLR yöntemlerle, sayı tabloları, çağrı CROSS APPLY
s ... En cevapları satır sayısı oldukça fazla kapsamaktadır.
Ancak - nth elementi elde etmek için gerçekten bir yaklaşımdan başka bir şey istemiyorsanız - bu gerçek bir astar , UDF yok, bir alt seçim bile değil ... Ve ekstra bir fayda olarak: güvenli yazın
Bölüm 2'yi boşlukla sınırlandırın:
DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')
Elbette , sınırlayıcı ve konum için değişkenleri kullanabilirsiniz ( sql:column
konumu doğrudan bir sorgu değerinden almak için kullanın ):
DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')
Dizeniz yasak karakterler (özellikle de bunlardan biri &><
) içeriyorsa , bunu yine de yapabilirsiniz. FOR XML PATH
Tüm yasak karakterleri örtülü kaçış dizisiyle değiştirmek için önce dizenizi kullanın .
Ek olarak - sınırlayıcınız noktalı virgülse çok özel bir durumdur . Bu durumda sınırlayıcıyı önce '# DLMT #' olarak değiştiririm ve bunu sonunda XML etiketlerine değiştiririm:
SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');
Maalesef geliştiriciler parçanın dizinini geri getirmeyi unuttu STRING_SPLIT
. Ancak, SQL-Server 2016+ kullanarak JSON_VALUE
ve vardır OPENJSON
.
İle JSON_VALUE
indeks dizisi olarak pozisyona geçebiliriz.
İçin dokümantasyon açıkça belirtmektedir:OPENJSON
OPENJSON bir JSON dizisini ayrıştırdığında, işlev JSON metnindeki öğelerin dizinlerini anahtar olarak döndürür.
Gibi bir dize 1,2,3
daha parantez daha ihtiyaçları şey: [1,2,3]
.
Gibi bir kelime dizisi this is an example
olmalı ["this","is","an","example"]
.
Bunlar çok kolay dize işlemleri. Sadece deneyin:
DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;
--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));
- Güvenli bir dize ayırıcı ( sıfır tabanlı ) için bunu görün:
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
Gelen bu yazı Çeşitli yaklaşımlar test edilmiş ve bu, buldum OPENJSON
gerçekten hızlı. Ünlü "delimitedSplit8k ()" yönteminden bile çok daha hızlı ...
Bir dizinin içindeki bir diziyi yalnızca ikiye katlayarak kullanabiliriz [[]]
. Bu, yazılı bir WITH
cümleye izin verir :
DECLARE @SomeDelimitedString VARCHAR(100)='part1|1|20190920';
DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');
SELECT @SomeDelimitedString AS TheOriginal
,@JsonArray AS TransformedToJSON
,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment VARCHAR(100) '$[0]'
,TheSecondFragment INT '$[1]'
,TheThirdFragment DATE '$[2]') ValuesFromTheArray
<x><![CDATA[x<&>x]]></x>
.
CDATA
-bölümler bununla da başa çıkabilir ... Ama oyuncu kadrosundan sonra gitti ( text()
örtük olarak kaçtı ). Kaputun altındaki sihri sevmiyorum , bu yüzden (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
- yaklaşımını tercih ederim . Bu bana daha temiz görünüyor ve yine de oluyor ... ( CDATA ve XML hakkında biraz daha ).
İşte bunu yapacak bir UDF. Sınırlandırılmış değerlerin bir tablosunu döndürür, üzerindeki tüm senaryoları denememiştir, ancak örneğiniz iyi çalışır.
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
@myString varchar(500),
@deliminator varchar(10)
)
RETURNS
@ReturnTable TABLE
(
-- Add the column definitions for the TABLE variable here
[id] [int] IDENTITY(1,1) NOT NULL,
[part] [varchar](50) NULL
)
AS
BEGIN
Declare @iSpaces int
Declare @part varchar(50)
--initialize spaces
Select @iSpaces = charindex(@deliminator,@myString,0)
While @iSpaces > 0
Begin
Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))
Insert Into @ReturnTable(part)
Select @part
Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))
Select @iSpaces = charindex(@deliminator,@myString,0)
end
If len(@myString) > 0
Insert Into @ReturnTable
Select @myString
RETURN
END
GO
Buna şöyle derdiniz:
Select * From SplitString('Hello John Smith',' ')
Düzenleme: len> 1 ile ayırıcıları aşağıdaki gibi işlemek için güncellenmiş çözüm:
select * From SplitString('Hello**John**Smith','**')
Burada basit bir çözüm yolu gönderiyorum
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
İşlevi böyle yürüt
select * from dbo.split('Hello John Smith',' ')
Bence siz bunu çok karmaşık hale getiriyorsunuz. Sadece bir CLR UDF oluşturun ve onunla bitirin.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;
public partial class UserDefinedFunctions {
[SqlFunction]
public static SqlString SearchString(string Search) {
List<string> SearchWords = new List<string>();
foreach (string s in Search.Split(new char[] { ' ' })) {
if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
SearchWords.Add(s);
}
}
return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
}
};
Kullanmaya string
ve values()
açıklamaya ne dersiniz ?
DECLARE @str varchar(max)
SET @str = 'Hello John Smith'
DECLARE @separator varchar(max)
SET @separator = ' '
DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))
SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)'
INSERT INTO @Splited
EXEC(@str)
SELECT * FROM @Splited
Sonuç kümesi elde edildi.
id item
1 Hello
2 John
3 Smith
Frederic yanıt kullanın ama bu SQL Server 2005'te işe yaramadı
Değiştirdim ve select
birlikte kullanıyorum union all
ve işe yarıyor
DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'
DECLARE @separator varchar(max)
SET @separator = ' '
DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))
SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT ''' + @str + ''' '
INSERT INTO @Splited
EXEC(@str)
SELECT * FROM @Splited
Ve sonuç kümesi:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
örtülü olarak saklı yordamı çağırır ve UDF'lerde saklı yordamları kullanamazsınız.
Bu desen iyi çalışıyor ve genelleştirebilirsiniz
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
not ALAN , İNDEKS ve TÜR .
Izin vermek gibi tanımlayıcıları ile bazı tablo izin
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Sonra yazabilirsiniz
SELECT Source = q.value('(/n[1])', 'varchar(10)'),
RecordType = q.value('(/n[2])', 'varchar(20)'),
RecordNumber = q.value('(/n[3])', 'int'),
Status = q.value('(/n[4])', 'varchar(5)')
FROM (
SELECT q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
FROM some_TABLE
) Q
tüm parçaları bölme ve döküm.
Veritabanınız 130 veya daha yüksek uyumluluk düzeyine sahipse , belirli bir öğeyi dizine göre almak için STRING_SPLIT işlevini OFFSET FETCH yan tümceleriyle birlikte kullanabilirsiniz .
Öğeyi dizin N'ye (sıfır tabanlı) almak için aşağıdaki kodu kullanabilirsiniz
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Veritabanınızın uyumluluk seviyesini kontrol etmek için şu kodu yürütün:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
-split tabanlı yaklaşımı tercih ederim , çünkü bu değer tipi-güvenli getirilmesini sağlar ve bir alt sorguya ihtiyaç duymaz, ancak bu bir İyi bir.
STRING_SPLIT
v2016 + için talepleri kullanma . Bu durumda OPENJSON
veya kullanmak çok daha iyidir JSON_VALUE
. Cevabımı kontrol etmek
Ben net bir çözüm arıyordum ve aşağıdaki benim için çalışıyor. Ref .
Ve işlevi şöyle çağırırsınız:
SELECT * FROM dbo.split('ram shyam hari gopal',' ')
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))
RETURNS @temptable TABLE (items VARCHAR(8000))
AS
BEGIN
DECLARE @idx INT
DECLARE @slice VARCHAR(8000)
SELECT @idx = 1
IF len(@String)<1 OR @String IS NULL RETURN
WHILE @idx!= 0
BEGIN
SET @idx = charindex(@Delimiter,@String)
IF @idx!=0
SET @slice = LEFT(@String,@idx - 1)
ELSE
SET @slice = @String
IF(len(@slice)>0)
INSERT INTO @temptable(Items) VALUES(@slice)
SET @String = RIGHT(@String,len(@String) - @idx)
IF len(@String) = 0 break
END
RETURN
END
Sınırlayıcı işleviyle dizenin başka bir parçası olsun:
create function GetStringPartByDelimeter (
@value as nvarchar(max),
@delimeter as nvarchar(max),
@position as int
) returns NVARCHAR(MAX)
AS BEGIN
declare @startPos as int
declare @endPos as int
set @endPos = -1
while (@position > 0 and @endPos != 0) begin
set @startPos = @endPos + 1
set @endPos = charindex(@delimeter, @value, @startPos)
if(@position = 1) begin
if(@endPos = 0)
set @endPos = len(@value) + 1
return substring(@value, @startPos, @endPos - @startPos)
end
set @position = @position - 1
end
return null
end
ve kullanımı:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
döndüren:
c
Bunu dene:
CREATE function [SplitWordList]
(
@list varchar(8000)
)
returns @t table
(
Word varchar(50) not null,
Position int identity(1,1) not null
)
as begin
declare
@pos int,
@lpos int,
@item varchar(100),
@ignore varchar(100),
@dl int,
@a1 int,
@a2 int,
@z1 int,
@z2 int,
@n1 int,
@n2 int,
@c varchar(1),
@a smallint
select
@a1 = ascii('a'),
@a2 = ascii('A'),
@z1 = ascii('z'),
@z2 = ascii('Z'),
@n1 = ascii('0'),
@n2 = ascii('9')
set @ignore = '''"'
set @pos = 1
set @dl = datalength(@list)
set @lpos = 1
set @item = ''
while (@pos <= @dl) begin
set @c = substring(@list, @pos, 1)
if (@ignore not like '%' + @c + '%') begin
set @a = ascii(@c)
if ((@a >= @a1) and (@a <= @z1))
or ((@a >= @a2) and (@a <= @z2))
or ((@a >= @n1) and (@a <= @n2))
begin
set @item = @item + @c
end else if (@item > '') begin
insert into @t values (@item)
set @item = ''
end
end
set @pos = @pos + 1
end
if (@item > '') begin
insert into @t values (@item)
end
return
end
Bu şekilde test edin:
select * from SplitWordList('Hello John Smith')
Aşağıdaki örnek, özyinelemeli bir CTE kullanır
Güncelleme 18.09.2013
CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
(
SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter, @List + @Delimiter)) AS val,
CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval,
1 AS [level]
UNION ALL
SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
[level] + 1
FROM cte
WHERE stval != ''
)
INSERT @returns
SELECT REPLACE(val, ' ','' ) AS val, [level]
FROM cte
WHERE val > ''
RETURN
END
Üzerinde Demosu SQLFiddle
Alter Function dbo.fn_Split
(
@Expression nvarchar(max),
@Delimiter nvarchar(20) = ',',
@Qualifier char(1) = Null
)
RETURNS @Results TABLE (id int IDENTITY(1,1), value nvarchar(max))
AS
BEGIN
/* USAGE
Select * From dbo.fn_Split('apple pear grape banana orange honeydew cantalope 3 2 1 4', ' ', Null)
Select * From dbo.fn_Split('1,abc,"Doe, John",4', ',', '"')
Select * From dbo.fn_Split('Hello 0,"&""&&&&', ',', '"')
*/
-- Declare Variables
DECLARE
@X xml,
@Temp nvarchar(max),
@Temp2 nvarchar(max),
@Start int,
@End int
-- HTML Encode @Expression
Select @Expression = (Select @Expression For XML Path(''))
-- Find all occurences of @Delimiter within @Qualifier and replace with |||***|||
While PATINDEX('%' + @Qualifier + '%', @Expression) > 0 AND Len(IsNull(@Qualifier, '')) > 0
BEGIN
Select
-- Starting character position of @Qualifier
@Start = PATINDEX('%' + @Qualifier + '%', @Expression),
-- @Expression starting at the @Start position
@Temp = SubString(@Expression, @Start + 1, LEN(@Expression)-@Start+1),
-- Next position of @Qualifier within @Expression
@End = PATINDEX('%' + @Qualifier + '%', @Temp) - 1,
-- The part of Expression found between the @Qualifiers
@Temp2 = Case When @End < 0 Then @Temp Else Left(@Temp, @End) End,
-- New @Expression
@Expression = REPLACE(@Expression,
@Qualifier + @Temp2 + Case When @End < 0 Then '' Else @Qualifier End,
Replace(@Temp2, @Delimiter, '|||***|||')
)
END
-- Replace all occurences of @Delimiter within @Expression with '</fn_Split><fn_Split>'
-- And convert it to XML so we can select from it
SET
@X = Cast('<fn_Split>' +
Replace(@Expression, @Delimiter, '</fn_Split><fn_Split>') +
'</fn_Split>' as xml)
-- Insert into our returnable table replacing '|||***|||' back to @Delimiter
INSERT @Results
SELECT
"Value" = LTRIM(RTrim(Replace(C.value('.', 'nvarchar(max)'), '|||***|||', @Delimiter)))
FROM
@X.nodes('fn_Split') as X(C)
-- Return our temp table
RETURN
END
SQL'de bir işleve ihtiyaç duymadan bir dizeyi bölebilirsiniz:
DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'
-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT
x.XmlCol.value('.', 'varchar(36)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);
Rasgele dizeleri (xml özel karakterleriyle) desteklemeniz gerekiyorsa
DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "Conex"<html>,Barnes & Noble,abc,def,ghi'
-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT
x.XmlCol.value('.', 'nvarchar(MAX)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);
Bunun eski bir soru olduğunu biliyorum, ama bazılarının benim çözümümden faydalanabileceğini düşünüyorum.
select
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
,1
,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
,LEN(column_name))
from table_name
Avantajları:
Sınırlamalar:
Not : çözüm N'ye kadar alt dize verebilir.
Sınırlamanın üstesinden gelmek için aşağıdaki ref .
Ama yine de yukarıdaki çözüm bir tabloda kullanılamaz (Actaully i kullanamadım).
Yine umarım bu çözüm bazılarına yardımcı olabilir.
Güncelleme: Kayıtlar> 50000 olması durumunda , Performansı düşüreceği için kullanılması önerilmezLOOPS
TVF
Özyinelemeli saf set tabanlı çözüm CTE
. Bunu JOIN
ve APPLY
herhangi bir veri kümesine bu işlevi yapabilirsiniz .
create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
union all
select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
, left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
, [no] + 1 [no]
from r where value > '')
select ltrim(x) [value], [no] [index] from r where x is not null;
go
Kullanımı:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Sonuç:
value index
-------------
John 1
Diğer tüm cevaplar, CPU döngülerini harcayan ve gereksiz bellek ayırma işlemleri gerçekleştiren bölünmüş dizenin yerini alıyor.
Ben burada bir dize bölünmüş yapmak için çok daha iyi bir yol kapsar: http://www.digitalruby.com/split-string-sql-server/
İşte kod:
SET NOCOUNT ON
-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1
SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
WHILE @SplitEndPos > 0
BEGIN
SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
INSERT @SplitStringTable (Value) VALUES (@SplitValue)
SET @SplitStartPos = @SplitEndPos + 1
SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END
SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)
SET NOCOUNT OFF
-- You can select or join with the values in @SplitStringTable at this point.
Sunucu ağrısı ile özyinelemeli CTE çözümü, test edin
MS SQL Server 2008 Şema Kurulumu :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Sorgu 1 :
with cte as
( select
left( Courses, charindex( ' ' , Courses) ) as a_l,
cast( substring( Courses,
charindex( ' ' , Courses) + 1 ,
len(Courses ) ) + ' '
as varchar(100) ) as a_r,
Courses as a,
0 as n
from Course t
union all
select
left(a_r, charindex( ' ' , a_r) ) as a_l,
substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
cte.a,
cte.n + 1 as n
from Course t inner join cte
on t.Courses = cte.a and len( a_r ) > 0
)
select a_l, n from cte
--where N = 1
Sonuçlar :
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
josejuan tarafından xml tabanlı cevap benzer iken, ben sadece bir kez xml yolu işleme, sonra pivoting orta derecede daha verimli bulundu:
select ID,
[3] as PathProvidingID,
[4] as PathProvider,
[5] as ComponentProvidingID,
[6] as ComponentProviding,
[7] as InputRecievingID,
[8] as InputRecieving,
[9] as RowsPassed,
[10] as InputRecieving2
from
(
select id,message,d.* from sysssislog cross apply (
SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
row_number() over(order by y.i) as rn
FROM
(
SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
) d
WHERE event
=
'OnPipelineRowsSent'
) as tokens
pivot
( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10])
) as data
8:30 da koştu
select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
from
(
select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
from sysssislog
WHERE event
=
'OnPipelineRowsSent'
) as data
koştu 9:20
CREATE FUNCTION [dbo].[fnSplitString]
(
@string NVARCHAR(MAX),
@delimiter CHAR(1)
)
RETURNS @output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE @start INT, @end INT
SELECT @start = 1, @end = CHARINDEX(@delimiter, @string)
WHILE @start < LEN(@string) + 1 BEGIN
IF @end = 0
SET @end = LEN(@string) + 1
INSERT INTO @output (splitdata)
VALUES(SUBSTRING(@string, @start, @end - @start))
SET @start = @end + 1
SET @end = CHARINDEX(@delimiter, @string, @start)
END
RETURN
END
VE KULLANIN
select *from dbo.fnSplitString('Querying SQL Server','')
eğer biri ayrılmış metnin sadece bir kısmını almak isterse bunu kullanabilir
seçinSplitStringSep ('Word1 wordr2 word3', '')
CREATE function [dbo].[SplitStringSep]
(
@str nvarchar(4000),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
1,
1,
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 zeroBasedOccurance,
substring(
@str,
a,
case when b > 0 then b-a ELSE 4000 end)
AS s
from tokens
)
Bunu geliştirdim,
declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';
while CHARINDEX(@splitter,@x) != 0
begin
set @item = LEFT(@x,CHARINDEX(@splitter,@x))
set @x = RIGHT(@x,len(@x)-len(@item) )
select @item as item, @x as x;
end
tek dikkat etmeniz gereken nokta '.' @x'in sonu her zaman orada olmalıdır.
imkansız bir çözüm, ya da daha çok, en çok oylanan cevap üzerine yorum (kabul edilenin hemen altında), aşağıdaki hızlı ve kirli çözümün kendi ihtiyaçlarımı karşıladığını gördüm - sadece SQL etki alanında olmanın bir yararı var.
bir dize verildi "birinci; ikinci; üçüncü; dördüncü; beşinci", diyelim, üçüncü belirteci almak istiyorum. Bu, yalnızca dizenin kaç jetonu olacağını bildiğimizde işe yarar - bu durumda 5'tir. bu yüzden benim eylemim son iki jetonu kesmek (iç sorgu) ve daha sonra ilk iki jetonu kesmek ( dış sorgu)
Ben bu çirkin olduğunu ve içinde bulunduğum belirli koşulları kapsar biliyorum, ama sadece birisi yararlı bulursa gönderirim. şerefe
select
REVERSE(
SUBSTRING(
reverse_substring,
0,
CHARINDEX(';', reverse_substring)
)
)
from
(
select
msg,
SUBSTRING(
REVERSE(msg),
CHARINDEX(
';',
REVERSE(msg),
CHARINDEX(
';',
REVERSE(msg)
)+1
)+1,
1000
) reverse_substring
from
(
select 'first;second;third;fourth;fifth' msg
) a
) b
İle başlayan SQL Server 2016 biz string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
aynı siparişi iade etmeyi garanti etmez. Ama OPENJSON
öyle ( cevabımı gör (güncelleme bölümü) )