Używając programu SQL Server, w jaki sposób podzielić ciąg, aby uzyskać dostęp do elementu x?
Weź ciąg „Hello John Smith”. Jak mogę podzielić ciąg według spacji i uzyskać dostęp do elementu o indeksie 1, który powinien zwrócić „John”?
Używając programu SQL Server, w jaki sposób podzielić ciąg, aby uzyskać dostęp do elementu x?
Weź ciąg „Hello John Smith”. Jak mogę podzielić ciąg według spacji i uzyskać dostęp do elementu o indeksie 1, który powinien zwrócić „John”?
Odpowiedzi:
Rozwiązanie w funkcji SQL User Defined Function służące do parsowania łańcucha rozdzielanego może być pomocne (z projektu Code ).
Możesz użyć tej prostej logiki:
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)))
nie SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
, która podzieli ciąg i zwróci wynik tabeli z jedną kolumną, której można użyć w SELECT
instrukcji lub w innym miejscu.
Nie wierzę, że SQL Server ma wbudowaną funkcję podziału, więc oprócz UDF jedyną znaną odpowiedzią jest przejęcie funkcji PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME bierze ciąg i dzieli go na znak kropki. Jako drugi argument przyjmuje liczbę, która określa, który segment łańcucha ma zostać zwrócony (działa od tyłu do przodu).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Oczywistym problemem jest to, że ciąg zawiera już kropkę. Nadal uważam, że korzystanie z UDF jest najlepszym sposobem ... jakieś inne sugestie?
SPLIT()
Funkcja nie jest dostarczana, ponieważ zachęca słabe projekt bazy danych, a baza danych nie zostanie zoptymalizowany do korzystania z danych zapisanych w tym formacie. RDBMS nie jest zobowiązany do pomocy programistów robić głupie rzeczy, że zostało zaprojektowane nie do uchwytu. Prawidłowa odpowiedź będzie zawsze brzmiała: „Normalizuj bazę danych, jak mówiliśmy 40 lat temu”. Ani SQL, ani RDBMS nie są winne złego projektu.
Najpierw utwórz funkcję (za pomocą CTE wspólne wyrażenie tabelowe eliminuje potrzebę tworzenia tabeli tymczasowej)
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
Następnie użyj go jako dowolnej tabeli (lub zmodyfikuj w celu dopasowania do istniejącego przechowywanego proc) w ten sposób.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Aktualizacja
Poprzednia wersja nie działała dla ciągu wejściowego dłuższego niż 4000 znaków. Ta wersja zajmuje się ograniczeniem:
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
Użycie pozostaje takie samo.
100
(aby zapobiec nieskończonej pętli). Użyj podpowiedzi MAXRECURSION, aby zdefiniować liczbę poziomów rekurencji ( 0
do 32767
, 0
to „no limit” - może zniszczyć serwer). BTW, znacznie lepsza odpowiedź niż PARSENAME
, ponieważ jest uniwersalna :-). +1
maxrecursion
do tego rozwiązania, pamiętaj o tym pytaniu i jego odpowiedziach. Jak ustawić maxrecursion
opcję CTE w funkcji wycenianej w tabeli .
s
nie jest już zdefiniowany
Większość rozwiązań tutaj wykorzystuje pętle while lub rekurencyjne CTE. Podejście oparte na zestawie będzie lepsze, obiecuję, jeśli możesz użyć separatora innego niż spacja:
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
);
Przykładowe użycie:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Wyniki:
----
blat
Możesz także dodać idx
żądaną funkcję jako argument do funkcji, ale zostawię to jako ćwiczenie czytelnikowi.
Nie można tego zrobić tylko z funkcją natywnąSTRING_SPLIT
dodaną w SQL Server 2016, ponieważ nie ma gwarancji, że dane wyjściowe będą renderowane w kolejności oryginalnej listy. Innymi słowy, jeśli zdasz, 3,6,1
wynik będzie prawdopodobnie w tej kolejności, ale może być 1,3,6
. Poprosiłem o pomoc społeczności w ulepszeniu wbudowanej funkcji tutaj:
Przy wystarczającej jakościowej informacji zwrotnej mogą rozważyć wprowadzenie niektórych z tych ulepszeń:
Więcej informacji na temat funkcji podziału, dlaczego (i udowodnij to), podczas gdy pętle i rekurencyjne CTE nie skalują się, i lepsze alternatywy, jeśli dzielenie ciągów pochodzących z warstwy aplikacji:
Jednak w SQL Server 2016 lub nowszym powinieneś przyjrzeć się STRING_SPLIT()
i STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
i uzyskano wynik: Wartość Hello ello llo lo o John ohn hn n smith mith ith th
Możesz wykorzystać tabelę liczb do parsowania łańcucha.
Utwórz tabelę liczb fizycznych:
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
Utwórz tabelę testową z 1000000 wierszami
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
Utwórz funkcję
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
Użycie (wyprowadza 3mil wierszy w 40s na moim laptopie)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
sprzątać
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
Wydajność tutaj nie jest niesamowita, ale wywołanie funkcji w tabeli zawierającej milion wierszy nie jest najlepszym pomysłem. Wykonując ciąg podzielony na wiele wierszy, unikałbym tej funkcji.
desc
zostały usunięte?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
od @NothingsImpossible zakończono w 1,5 minuty. @hello_earth Jak Twoje rozwiązanie porównałoby się z dłuższymi ciągami z więcej niż 4 polami?
To pytanie nie dotyczy podejścia opartego na dzieleniu łańcucha , ale sposobu uzyskania n-tego elementu .
Wszystkie odpowiedzi są tu robi jakieś rozszczepienie łańcucha za pomocą rekurencji, CTE
s, stwardnienie CHARINDEX
, REVERSE
a PATINDEX
funkcje wymyślanie, wezwanie do metod numerycznych CLR, stoły, CROSS APPLY
s ... Większość odpowiedzi obejmują wiele linii kodu.
Ale - jeśli naprawdę nie chcesz niczego więcej niż podejście do uzyskania n-tego elementu - można to zrobić jako prawdziwy jeden liniowiec , bez UDF, nawet bez wyboru podrzędnego ... I jako dodatkowa korzyść: wpisz bezpieczny
Uzyskaj część 2 oddzieloną spacją:
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)')
Oczywiście możesz użyć zmiennych dla separatora i pozycji (użyj, sql:column
aby pobrać pozycję bezpośrednio z wartości zapytania):
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)')
Jeśli Twój ciąg może zawierać niedozwolone znaki (szczególnie jeden spośród &><
), nadal możesz to zrobić w ten sposób. Po prostu użyj FOR XML PATH
najpierw swojego ciągu, aby domyślnie zastąpić wszystkie zabronione znaki pasującą sekwencją ucieczki.
Jest to bardzo szczególny przypadek, jeśli - dodatkowo - separatorem jest średnik . W takim przypadku najpierw zastępuję separator na „# DLMT #”, a na końcu zastępuję go tagami XML:
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)');
Niestety programiści zapomnieli zwrócić indeks części STRING_SPLIT
. Ale przy użyciu SQL Server 2016+ istnieje JSON_VALUE
i OPENJSON
.
Za pomocą JSON_VALUE
możemy podać pozycję jako tablicę indeksu.
Dla dokumentacja stwierdza wyraźnie:OPENJSON
Kiedy OPENJSON analizuje tablicę JSON, funkcja zwraca indeksy elementów w tekście JSON jako klucze.
Ciąg jak 1,2,3
potrzebuje niczego więcej niż nawiasach: [1,2,3]
.
Ciąg takich słów this is an example
musi być ["this","is","an","example"]
.
Są to bardzo łatwe operacje na łańcuchach. Po prostu wypróbuj:
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,']'));
- Zobacz to dla dzielnika łańcuchów bezpiecznego pozycjonowania (od zera ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
W tym poście przetestowałem różne podejścia i stwierdziłem, że OPENJSON
jest to naprawdę szybkie. Nawet znacznie szybszy niż słynna metoda „delimitedSplit8k ()” ...
Możemy użyć tablicy w tablicy po prostu używając podwójnej [[]]
. Pozwala to na WITH
wpisanie klauzuli:
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
mogą sobie z tym poradzić ... Ale po obsadzie zniknęły (zmieniono na ucieczkę text()
niejawnie). Nie podoba mi się magia pod maską , więc wolę (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
podejście -. Wydaje mi się to czystsze i tak się dzieje ... (Więcej o CDATA i XML ).
Oto UDF, który to zrobi. Zwróci tabelę wartości rozdzielanych, nie wypróbowałem na niej wszystkich scenariuszy, ale twój przykład działa dobrze.
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
Nazwałbyś to tak:
Select * From SplitString('Hello John Smith',' ')
Edycja: Zaktualizowano rozwiązanie do obsługi ograniczników z długością> 1 jak w:
select * From SplitString('Hello**John**Smith','**')
Tutaj zamieszczam prosty sposób rozwiązania
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
Wykonaj taką funkcję
select * from dbo.split('Hello John Smith',' ')
Moim zdaniem, robicie to zbyt skomplikowane. Wystarczy utworzyć CLR UDF i gotowe.
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()));
}
};
Co z używaniem string
ivalues()
oświadczeniem?
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
Zestaw wyników osiągnięty.
id item
1 Hello
2 John
3 Smith
Korzystam z odpowiedzi frederic, ale to nie działało w SQL Server 2005
I zmodyfikowano go i używam select
z union all
i działa
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
A zestaw wyników to:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
niejawnie wywołuje procedurę przechowywaną i nie można używać procedur przechowywanych w UDF.
Ten wzór działa dobrze i możesz uogólniać
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
uwaga POLA , INDEKS i TYP .
Niech trochę tabela z identyfikatorami, takimi jak
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Następnie możesz pisać
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
dzielenie i odlewanie wszystkich części.
Jeśli Twoja baza danych ma poziom zgodności 130 lub wyższy, możesz użyć funkcji STRING_SPLIT wraz z klauzulami OFFSET FETCH , aby uzyskać określony element według indeksu.
Aby uzyskać pozycję o indeksie N (zero), możesz użyć następującego kodu
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Aby sprawdzić poziom zgodności bazy danych , wykonaj ten kod:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
używałam tego wcześniej ... Dobrze wiedzieć ... Nadal wolałbym podejście oparte na -split, ponieważ pozwala ono pobrać wartość bezpieczną dla typu i nie wymaga sub-zapytania, ale jest to dobry. +1 z mojej strony
STRING_SPLIT
żądań dla v2016 +. W takim przypadku znacznie lepiej jest użyć OPENJSON
lub JSON_VALUE
. Może chcesz sprawdzić moją odpowiedź
Szukałem rozwiązania w sieci, a poniższe działa dla mnie. Nr ref .
I wywołujesz funkcję w ten sposób:
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
Jeszcze inna zdobądź n-tą część ciągu przez funkcję delimera:
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
i wykorzystanie:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
który zwraca:
c
Spróbuj tego:
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
Przetestuj tak:
select * from SplitWordList('Hello John Smith')
W poniższym przykładzie użyto rekurencyjnego CTE
Aktualizacja 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
Demo na 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
Możesz podzielić ciąg znaków w SQL bez potrzeby używania funkcji:
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);
Jeśli potrzebujesz obsługi dowolnych ciągów znaków (ze znakami specjalnymi xml)
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);
Wiem, że to stare pytanie, ale myślę, że ktoś może skorzystać z mojego rozwiązania.
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
Zalety:
Ograniczenia:
Uwaga : rozwiązanie może dać ciąg podrzędny do N.
Aby pokonać ograniczenie, możemy skorzystać z następującego ref .
Ale znowu powyższego rozwiązania nie można użyć w tabeli (Actaully nie byłem w stanie go użyć).
Ponownie mam nadzieję, że to rozwiązanie może komuś pomóc.
Aktualizacja: W przypadku rekordów> 50000 nie zaleca się używania, LOOPS
ponieważ spowoduje to obniżenie wydajności
Rozwiązanie oparte na czystym zestawie TVF
z rekurencyjnym CTE
. Możesz JOIN
i APPLY
ta funkcja do dowolnego zestawu danych.
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
Stosowanie:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Wynik:
value index
-------------
John 1
Prawie wszystkie inne odpowiedzi zastępują dzielony ciąg, który marnuje cykle procesora i wykonuje niepotrzebne przydziały pamięci.
Omawiam tutaj znacznie lepszy sposób wykonania podziału strun: http://www.digitalruby.com/split-string-sql-server/
Oto 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.
Rekurencyjne rozwiązanie CTE z bólem serwera, przetestuj je
Konfiguracja schematu MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Zapytanie 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
Wyniki :
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
chociaż podobny do odpowiedzi opartej na xml autorstwa josejuan, stwierdziłem, że przetwarzanie ścieżki xml tylko raz, następnie przestawianie było umiarkowanie bardziej wydajne:
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
pobiegł w 8:30
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
pobiegł w 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
I UŻYWAJ GO
select *from dbo.fnSplitString('Querying SQL Server','')
jeśli ktoś chce uzyskać tylko jedną część oddzielnego tekstu, może to wykorzystać
wybierz * z fromSplitStringSep („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
)
Zdewoluowałem to,
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
jedyną uwagę, na którą powinieneś zwrócić uwagę, jest kropka ”. koniec @x zawsze powinien być na miejscu.
Opierając się na @NothingsImpossible rozwiązanie, a raczej komentować najczęściej głosowaną odpowiedź (tuż poniżej zaakceptowanej), znalazłem następujący szybki i brudny rozwiązanie, które spełnia moje własne potrzeby - ma tę zaletę, że znajduje się wyłącznie w domenie SQL.
biorąc ciąg „pierwszy; drugi; trzeci; czwarty; piąty”, powiedzmy, chcę uzyskać trzeci token. działa to tylko wtedy, gdy wiemy, ile tokenów będzie miał łańcuch - w tym przypadku jest to 5. więc moim sposobem działania jest odrąbanie dwóch ostatnich tokenów (zapytanie wewnętrzne), a następnie oderwanie dwóch pierwszych tokenów ( zapytanie zewnętrzne)
wiem, że jest to brzydkie i obejmuje określone warunki, w których byłem, ale publikuję to na wypadek, gdyby ktoś uznał to za przydatne. Twoje zdrowie
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
Począwszy od SQL Server 2016 my string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
nie gwarantuje zwrotu tego samego zamówienia. Ale OPENJSON
tak (patrz moja odpowiedź (sekcja aktualizacji) )