Menggunakan SQL Server, bagaimana cara membagi string sehingga saya dapat mengakses item x?
Ambil string "Hello John Smith". Bagaimana saya bisa membagi string dengan ruang dan mengakses item di indeks 1 yang seharusnya mengembalikan "John"?
Menggunakan SQL Server, bagaimana cara membagi string sehingga saya dapat mengakses item x?
Ambil string "Hello John Smith". Bagaimana saya bisa membagi string dengan ruang dan mengakses item di indeks 1 yang seharusnya mengembalikan "John"?
Jawaban:
Anda dapat menemukan solusi dalam Fungsi Ditentukan Pengguna SQL untuk Parse a Delimited String bermanfaat (dari Proyek Kode ).
Anda dapat menggunakan logika sederhana ini:
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)))
dan tidak SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
yang akan membagi string dan mengembalikan hasil tabel satu kolom yang bisa Anda gunakan dalam SELECT
pernyataan atau di tempat lain.
Saya tidak percaya SQL Server memiliki fungsi split bawaan, jadi selain UDF, satu-satunya jawaban lain yang saya tahu adalah membajak fungsi PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME mengambil string dan membaginya pada karakter periode. Dibutuhkan angka sebagai argumen kedua, dan angka itu menentukan segmen string mana yang akan kembali (bekerja dari belakang ke depan).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Masalah yang jelas adalah ketika string sudah mengandung tanda titik. Saya masih berpikir menggunakan UDF adalah cara terbaik ... ada saran lain?
SPLIT()
fungsi ini tidak disertakan karena mendorong desain database miskin, dan database akan pernah dioptimalkan untuk menggunakan data yang disimpan dalam format ini. RDBMS tidak berkewajiban membantu pengembang melakukan hal-hal bodoh yang telah dirancang untuk tidak ditangani. Jawaban yang benar akan selalu "Menormalkan database Anda seperti yang kami katakan 40 tahun yang lalu." SQL atau RDBMS tidak dapat disalahkan untuk desain yang buruk.
Pertama, buat fungsi (menggunakan CTE, ekspresi tabel umum tidak jauh dengan kebutuhan untuk tabel temp)
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
Kemudian, gunakan sebagai tabel apa saja (atau modifikasi agar sesuai dengan proc tersimpan Anda yang sudah ada) seperti ini.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Memperbarui
Versi sebelumnya akan gagal untuk string input yang lebih panjang dari 4000 karakter. Versi ini menangani batasan:
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
Penggunaannya tetap sama.
100
(untuk mencegah infinite loop). Gunakan petunjuk MAXRECURSION untuk menentukan jumlah level rekursi ( 0
to 32767
, 0
adalah "no limit" - dapat menghancurkan server). BTW, jawabannya jauh lebih baik daripada PARSENAME
, karena itu universal :-). +1
maxrecursion
ke solusi ini perlu diingat pertanyaan ini dan jawabannya Bagaimana mengatur maxrecursion
opsi untuk CTE di dalam Table-Valued-Function .
s
tidak lagi ditentukan
Sebagian besar solusi di sini digunakan saat loop atau CTE rekursif. Pendekatan berbasis set akan lebih unggul, saya janji, jika Anda dapat menggunakan pembatas selain spasi:
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
);
Penggunaan sampel:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Hasil:
----
blat
Anda juga bisa menambahkan yang idx
Anda inginkan sebagai argumen ke fungsi, tetapi saya akan membiarkannya sebagai latihan bagi pembaca.
Anda tidak dapat melakukan ini hanya dengan fungsi asli yangSTRING_SPLIT
ditambahkan di SQL Server 2016, karena tidak ada jaminan bahwa output akan diberikan dalam urutan daftar asli. Dengan kata lain, jika Anda lulus dalam 3,6,1
hasil kemungkinan akan berada di urutan itu, tetapi bisa jadi 1,3,6
. Saya telah meminta bantuan komunitas dalam meningkatkan fungsi bawaan di sini:
Dengan umpan balik kualitatif yang cukup , mereka mungkin benar-benar mempertimbangkan untuk membuat beberapa peningkatan ini:
Lebih lanjut tentang fungsi split, mengapa (dan buktikan itu) sementara loop dan CTE rekursif tidak skala, dan alternatif yang lebih baik, jika memisahkan string yang berasal dari lapisan aplikasi:
Pada SQL Server 2016 atau lebih baru, Anda harus melihat STRING_SPLIT()
dan STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
dan output yang dihasilkan adalah: Nilai Halo ello llo lo o John ohn hn n smith mith ith th h
Anda bisa memanfaatkan tabel angka untuk melakukan penguraian string.
Buat tabel angka fisik:
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
Buat tabel uji dengan 10.00000 baris
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
Buat fungsinya
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
Penggunaan (menghasilkan 3mil baris dalam 40-an di laptop saya)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
membersihkan
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
Performa di sini tidak luar biasa, tetapi memanggil fungsi lebih dari satu juta tabel baris bukanlah ide terbaik. Jika melakukan split string pada banyak baris saya akan menghindari fungsi.
desc
sudah dihapus?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
dari @NothingsImpossible diselesaikan dalam 1,5 menit. @hello_earth Bagaimana solusi Anda membandingkan string yang lebih panjang dengan lebih dari 4 bidang?
Pertanyaan ini bukan tentang pendekatan string split , tetapi tentang bagaimana mendapatkan elemen ke-n .
Semua jawaban di sini sedang melakukan semacam pemisahan string menggunakan rekursi, CTE
s, banyak CHARINDEX
, REVERSE
dan PATINDEX
, menciptakan fungsi, panggilan untuk metode CLR, tabel angka, CROSS APPLY
s ... Sebagian besar jawaban mencakup banyak baris kode.
Tetapi - jika Anda benar - benar menginginkan tidak lebih dari sebuah pendekatan untuk mendapatkan elemen ke-n - ini dapat dilakukan sebagai one-liner nyata , tanpa UDF, bahkan bukan sub-pilih ... Dan sebagai manfaat tambahan: ketik aman
Dapatkan bagian 2 dibatasi oleh spasi:
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)')
Tentu saja Anda dapat menggunakan variabel untuk pembatas dan posisi (gunakan sql:column
untuk mengambil posisi secara langsung dari nilai kueri):
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)')
Jika string Anda mungkin menyertakan karakter terlarang (terutama satu di antaranya &><
), Anda masih bisa melakukannya dengan cara ini. Cukup gunakan FOR XML PATH
pada string Anda terlebih dahulu untuk mengganti semua karakter terlarang dengan urutan pelepasan yang sesuai secara implisit.
Ini adalah kasus yang sangat istimewa jika - tambahan - pembatas Anda adalah titik koma . Dalam hal ini saya mengganti pembatas pertama ke '# DLMT #', dan akhirnya ganti ini dengan tag 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)');
Dengan menyesal para pengembang lupa untuk mengembalikan indeks bagian itu STRING_SPLIT
. Tapi, menggunakan SQL-Server 2016+, ada JSON_VALUE
dan OPENJSON
.
Dengan JSON_VALUE
kita dapat lewat di posisi sebagai array indeks.
Untuk OPENJSON
itu dokumentasi menyatakan dengan jelas:
Ketika OPENJSON mem-parsing array JSON, fungsi mengembalikan indeks elemen-elemen dalam teks JSON sebagai kunci.
Sebuah string seperti 1,2,3
kebutuhan tidak lebih dari kurung: [1,2,3]
.
Sederetan kata-kata seperti this is an example
perlu ["this","is","an","example"]
.
Ini adalah operasi string yang sangat mudah. Coba saja:
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,']'));
- Lihat ini untuk pemisah tali yang aman ( berbasis nol ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
Dalam posting ini saya menguji berbagai pendekatan dan menemukan, itu OPENJSON
sangat cepat. Bahkan jauh lebih cepat daripada metode "delimitedSplit8k ()" yang terkenal ...
Kita bisa menggunakan array dalam array hanya dengan menggunakan doubled [[]]
. Ini memungkinkan untuk sebuah WITH
klausa yang diketik :
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
-bagian bisa menangani ini juga ... Tapi setelah para pemain mereka pergi (diubah menjadi melarikan diri text()
secara implisit). Saya tidak suka sihir di bawah tenda , jadi saya lebih suka (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
pendekatan -. Ini terlihat lebih bersih bagi saya dan tetap terjadi ... (Lebih banyak tentang CDATA dan XML ).
Inilah UDF yang akan melakukannya. Ini akan mengembalikan tabel nilai yang dibatasi, belum mencoba semua skenario di atasnya tetapi contoh Anda berfungsi dengan baik.
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
Anda akan menyebutnya seperti ini:
Select * From SplitString('Hello John Smith',' ')
Sunting: Solusi yang diperbarui untuk menangani pembatas dengan len> 1 seperti pada:
select * From SplitString('Hello**John**Smith','**')
Di sini saya memposting cara solusi sederhana
CREATE FUNCTION [dbo].[split](
@delimited NVARCHAR(MAX),
@delimiter NVARCHAR(100)
) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE @xml XML
SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'
INSERT INTO @t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM @xml.nodes('/t') as records(r)
RETURN
END
Jalankan fungsi seperti ini
select * from dbo.split('Hello John Smith',' ')
Menurut pendapat saya kalian membuatnya terlalu rumit. Cukup buat CLR UDF dan lakukan saja.
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()));
}
};
Bagaimana dengan penggunaan string
dan values()
pernyataan?
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
Hasil-set tercapai.
id item
1 Hello
2 John
3 Smith
Saya menggunakan jawaban frederic tetapi ini tidak berhasil di SQL Server 2005
Aku diubah dan saya menggunakan select
dengan union all
dan bekerja
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
Dan hasil-set adalah:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
secara implisit memanggil prosedur tersimpan, dan Anda tidak dapat menggunakan prosedur tersimpan di UDF.
Pola ini berfungsi dengan baik dan Anda dapat menggeneralisasi
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
perhatikan BIDANG , INDEKS dan JENIS .
Biarkan beberapa tabel dengan pengenal suka
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Lalu, Anda bisa menulis
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
membelah dan casting semua bagian.
Jika database Anda memiliki tingkat kompatibilitas 130 atau lebih tinggi maka Anda dapat menggunakan fungsi STRING_SPLIT bersama dengan klausa OFFSET FETCH untuk mendapatkan item tertentu dengan indeks.
Untuk mendapatkan item pada indeks N (berbasis nol), Anda dapat menggunakan kode berikut
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Untuk memeriksa tingkat kompatibilitas basis data Anda , jalankan kode ini:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
pendekatan berbasis-duduk, karena memungkinkan untuk mengambil tipe nilai-aman dan tidak perlu sub-permintaan, tapi ini adalah bagus +1 dari sisi saya
STRING_SPLIT
tuntutan untuk v2016 +. Dalam hal ini lebih baik menggunakan OPENJSON
atau JSON_VALUE
. Anda mungkin ingin memeriksa jawaban saya
Saya mencari solusi di internet dan di bawah ini berfungsi untuk saya. Ref .
Dan Anda memanggil fungsi seperti ini:
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
Namun bagian lain dari string dengan fungsi delimeter:
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
dan penggunaannya:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
yang mengembalikan:
c
Coba ini:
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
Uji seperti ini:
select * from SplitWordList('Hello John Smith')
Contoh berikut menggunakan CTE rekursif
Perbarui 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 di 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
Anda dapat membagi string dalam SQL tanpa membutuhkan fungsi:
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);
Jika Anda perlu mendukung string acak (dengan karakter khusus 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);
Saya tahu ini adalah pertanyaan lama, tetapi saya pikir seseorang dapat mengambil manfaat dari solusi saya.
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
Keuntungan:
Keterbatasan:
Catatan : solusinya dapat memberikan sub-string hingga N.
Untuk mengatasi batasan kita dapat menggunakan referensi berikut .
Tetapi sekali lagi solusi di atas tidak dapat digunakan dalam sebuah tabel (Actaully saya tidak dapat menggunakannya).
Sekali lagi saya berharap solusi ini dapat membantu seseorang.
Pembaruan: Dalam hal Catatan> 50000 tidak disarankan untuk digunakan LOOPS
karena akan menurunkan Kinerja
Solusi berbasis set murni menggunakan TVF
dengan rekursif CTE
. Anda bisa JOIN
dan APPLY
fungsi ini untuk dataset apa pun.
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
Pemakaian:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Hasil:
value index
-------------
John 1
Hampir semua jawaban lain menggantikan string yang sedang dibagi yang membuang siklus CPU dan melakukan alokasi memori yang tidak perlu.
Saya membahas cara yang jauh lebih baik untuk melakukan pemisahan string di sini: http://www.digitalruby.com/split-string-sql-server/
Ini kodenya:
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.
Solusi CTE rekursif dengan nyeri server, ujilah
Setup Skema MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Pertanyaan 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
Hasil :
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
sementara mirip dengan jawaban berbasis xml oleh josejuan, saya menemukan bahwa memproses jalur xml hanya sekali, kemudian pivoting cukup efisien:
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
berlari jam 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
berlari di 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
DAN GUNAKAN ITU
select *from dbo.fnSplitString('Querying SQL Server','')
jika ada yang ingin mendapatkan hanya satu bagian dari teks terpisah dapat menggunakan ini
pilih * dari 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
)
Saya memindahkan ini,
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
satu-satunya perhatian yang harus Anda perhatikan adalah titik '.' ujung @x harus selalu ada di sana.
membangun pada @NothingsImpossible solusi, atau, lebih tepatnya, mengomentari jawaban yang paling banyak dipilih (tepat di bawah yang diterima), saya menemukan solusi cepat dan kotor berikut memenuhi kebutuhan saya sendiri - itu memiliki manfaat hanya berada dalam domain SQL.
diberi string "pertama; kedua; ketiga; keempat; kelima", katakan, saya ingin mendapatkan token ketiga. ini bekerja hanya jika kita tahu berapa banyak token yang akan dimiliki string - dalam kasus ini 5., jadi cara tindakan saya adalah memotong dua token terakhir (permintaan dalam), dan kemudian memotong dua token pertama pergi ( permintaan luar)
saya tahu bahwa ini jelek dan mencakup kondisi spesifik saya, tetapi saya mempostingnya kalau-kalau ada yang merasa berguna. Bersulang
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
Dimulai dengan SQL Server 2016 kami string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
tidak menjamin untuk mengembalikan pesanan yang sama. Tapi OPENJSON
apakah (lihat jawaban saya (bagian pembaruan) )