การใช้ SQL Server ฉันจะแยกสตริงเพื่อให้สามารถเข้าถึงรายการ x ได้อย่างไร
ใช้สตริง "Hello John Smith" ฉันจะแบ่งสตริงตามช่องว่างและเข้าถึงรายการที่ดัชนี 1 ซึ่งควรส่งคืน "John" ได้อย่างไร
การใช้ SQL Server ฉันจะแยกสตริงเพื่อให้สามารถเข้าถึงรายการ x ได้อย่างไร
ใช้สตริง "Hello John Smith" ฉันจะแบ่งสตริงตามช่องว่างและเข้าถึงรายการที่ดัชนี 1 ซึ่งควรส่งคืน "John" ได้อย่างไร
คำตอบ:
คุณอาจพบวิธีแก้ปัญหาในSQL User Defined Function การแยกสตริงที่มีประโยชน์ (จากโครงการรหัส )
คุณสามารถใช้ตรรกะง่ายๆนี้:
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)))
ไม่ SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
ที่จะแยกสตริงและส่งกลับผลลัพธ์ตารางหนึ่งคอลัมน์ซึ่งคุณสามารถใช้ในSELECT
คำสั่งหรือที่อื่น ๆ
ฉันไม่เชื่อว่า SQL Server มีฟังก์ชั่นแยกในตัวดังนั้นนอกเหนือจาก UDF คำตอบอื่น ๆ ที่ฉันรู้คือการจี้ฟังก์ชั่น PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME รับสตริงและแยกสตริงตามอักขระช่วงเวลา มันต้องใช้ตัวเลขเป็นอาร์กิวเมนต์ที่สองและหมายเลขนั้นจะระบุเซ็กเมนต์ของสตริงที่จะส่งคืน (ทำงานจากกลับไปข้างหน้า)
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
ปัญหาที่ชัดเจนคือเมื่อสตริงมีจุดอยู่แล้ว ฉันยังคิดว่าการใช้ UDF เป็นวิธีที่ดีที่สุด ... คำแนะนำอื่น ๆ
SPLIT()
ฟังก์ชั่นไม่ได้ให้เพราะมันส่งเสริมการออกแบบฐานข้อมูลที่ไม่ดีและฐานข้อมูลจะไม่ได้รับการเพิ่มประสิทธิภาพในการใช้ข้อมูลที่เก็บไว้ในรูปแบบนี้ RDBMS ไม่มีข้อผูกมัดที่จะช่วยให้นักพัฒนาทำสิ่งที่โง่ที่มันถูกออกแบบมาไม่ให้จัดการ คำตอบที่ถูกต้องจะเสมอเป็น "ปกติฐานข้อมูลของคุณเหมือนที่เราบอกคุณ 40 ปีที่ผ่านมา." ทั้ง SQL และ RDBMS ไม่ควรตำหนิสำหรับการออกแบบที่ไม่ดี
ขั้นแรกให้สร้างฟังก์ชั่น (โดยใช้ CTE นิพจน์ตารางทั่วไปจะหายไปโดยไม่จำเป็นต้องใช้ตารางชั่วคราว)
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
จากนั้นใช้เป็นตารางใด ๆ (หรือแก้ไขเพื่อให้พอดีกับ proc ที่จัดเก็บอยู่ของคุณ) เช่นนี้
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
ปรับปรุง
รุ่นก่อนหน้าจะล้มเหลวในการใส่สายอักขระที่ยาวเกิน 4,000 ตัวอักษร รุ่นนี้ดูแลข้อ จำกัด :
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
การใช้งานยังคงเหมือนเดิม
100
(เพื่อป้องกันการวนซ้ำไม่สิ้นสุด) ใช้MAXRECURSION คำแนะนำในการกำหนดจำนวนของระดับการเรียกซ้ำ ( 0
ไป32767
, 0
คือ "ไม่ จำกัด" - อาจบดขยี้เซิร์ฟเวอร์) BTW คำตอบที่ดีกว่าPARSENAME
เพราะมันเป็นสากล :-) +1
maxrecursion
เพื่อให้การแก้ปัญหานี้ในใจคำถามนี้และคำตอบของวิธีการตั้งค่าmaxrecursion
ตัวเลือกสำหรับการ CTE ภายในตารางมูลค่า-ฟังก์ชัน
s
ไม่ได้กำหนดไว้อีกต่อไป
โซลูชันส่วนใหญ่ที่นี่ใช้ในขณะที่ลูปหรือ CTE แบบเรียกซ้ำ วิธีการตั้งค่าจะดีกว่าฉันสัญญาถ้าคุณสามารถใช้ตัวคั่นอื่นที่ไม่ใช่ช่องว่าง:
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
);
ตัวอย่างการใช้งาน:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
ผล:
----
blat
คุณสามารถเพิ่มสิ่งที่idx
คุณต้องการเป็นอาร์กิวเมนต์ให้กับฟังก์ชันได้ แต่ฉันจะปล่อยให้สิ่งนั้นเป็นแบบฝึกหัดให้กับผู้อ่าน
คุณไม่สามารถทำเช่นนี้กับเพียงพื้นเมืองของSTRING_SPLIT
ฟังก์ชั่นเพิ่มใน SQL Server 2016 เพราะมีการรับประกันว่าการส่งออกจะแสดงผลในคำสั่งของรายการเดิมไม่มี กล่าวอีกนัยหนึ่งถ้าคุณส่งผ่าน3,6,1
ผลลัพธ์จะเป็นไปตามลำดับ แต่อาจเป็น1,3,6
ไปได้ ฉันขอความช่วยเหลือจากชุมชนในการปรับปรุงฟังก์ชั่นในตัวที่นี่:
ด้วยความคิดเห็นเชิงคุณภาพที่เพียงพอพวกเขาอาจพิจารณาทำการปรับปรุงบางอย่างเหล่านี้:
เพิ่มเติมเกี่ยวกับฟังก์ชั่นแยกทำไม (และพิสูจน์ว่า) ในขณะที่ลูปและ CTE แบบเรียกซ้ำไม่ปรับขนาดและทางเลือกที่ดีกว่าถ้าแยกสตริงที่มาจากชั้นแอปพลิเคชัน:
บน SQL Server 2016 หรือสูงกว่าคุณควรดูSTRING_SPLIT()
และSTRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
และผลลัพธ์ที่ได้คือ: ค่าสวัสดี ello llo o John ohn hn n smith mith ith th h
คุณสามารถใช้ประโยชน์จากตาราง Number เพื่อทำการแยกสตริง
สร้างตารางหมายเลขทางกายภาพ:
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 แถว
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
สร้างฟังก์ชั่น
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
การใช้งาน (เอาต์พุต 3 ล้านแถวใน 40 วินาทีบนแล็ปท็อปของฉัน)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
ทำความสะอาด
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
ประสิทธิภาพที่นี่ไม่น่าทึ่ง แต่การเรียกใช้ฟังก์ชั่นที่มีมากกว่าหนึ่งล้านแถวนั้นไม่ใช่ความคิดที่ดีที่สุด หากทำการแยกสตริงด้วยหลายแถวฉันจะหลีกเลี่ยงการทำงาน
desc
นำออกหรือไม่
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
จาก @NothingsImpossible เสร็จใน 1.5 นาที @hello_earth โซลูชันของคุณจะเปรียบเทียบกับสตริงที่ยาวกว่าด้วยฟิลด์มากกว่า 4 ฟิลด์อย่างไร
คำถามนี้เป็นคำถามไม่ได้เกี่ยวกับวิธีการแยกสตริงแต่เกี่ยวกับวิธีการที่จะได้รับองค์ประกอบที่ n
คำตอบทั้งหมดที่นี่กำลังทำชนิดของการแยกสตริงโดยใช้การเรียกซ้ำ, CTE
S, หลายCHARINDEX
, REVERSE
และPATINDEX
ประดิษฐ์ฟังก์ชั่นการโทรหาวิธีการ CLR ตารางหมายเลขCROSS APPLY
s ... คำตอบส่วนใหญ่ครอบคลุมหลายสายรหัส
แต่ - ถ้าคุณไม่ต้องการอะไรมากไปกว่าการได้องค์ประกอบที่ n - สิ่งนี้สามารถทำได้เหมือนจริงแบบหนึ่งซับไม่มี UDF ไม่แม้แต่เลือกย่อย ... และประโยชน์พิเศษ: พิมพ์อย่างปลอดภัย
รับส่วนที่ 2 คั่นด้วยช่องว่าง:
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)')
แน่นอนคุณสามารถใช้ตัวแปรสำหรับตัวคั่นและตำแหน่ง (ใช้sql:column
เพื่อดึงตำแหน่งโดยตรงจากค่าของแบบสอบถาม):
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)')
หากสตริงของคุณอาจมีตัวอักษรต้องห้าม (โดยเฉพาะอย่างยิ่งหนึ่งในนั้น&><
) คุณยังสามารถทำเช่นนี้ได้ เพียงใช้FOR XML PATH
กับสตริงของคุณก่อนเพื่อแทนที่อักขระที่ต้องห้ามทั้งหมดด้วยลำดับการหลีกเลี่ยงที่เหมาะสมโดยปริยาย
มันเป็นกรณีพิเศษมากถ้า - นอกจากนี้ - คั่นของคุณอัฒภาค ในกรณีนี้ฉันแทนที่ตัวคั่นเป็น '# DLMT #' และแทนที่สิ่งนี้เป็นแท็ก 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)');
STRING_SPLIT
น่าเศร้านักพัฒนาลืมที่จะกลับมาเป็นส่วนหนึ่งของดัชนีที่มี แต่ใช้ SQL เซิร์ฟเวอร์ 2016+ มีและJSON_VALUE
OPENJSON
ด้วยJSON_VALUE
เราสามารถผ่านในฐานะอาร์เรย์ของดัชนี
สำหรับเอกสารระบุอย่างชัดเจนOPENJSON
เมื่อ OPENJSON แยกวิเคราะห์อาร์เรย์ JSON ฟังก์ชันจะส่งคืนดัชนีขององค์ประกอบในข้อความ JSON เป็นคีย์
สตริงเช่นความต้องการอะไรมากไปกว่าวงเล็บ:1,2,3
สตริงของคำเช่นความต้องการที่จะเป็น
การดำเนินการของสตริงเหล่านี้ง่ายมาก ลองใช้ดูสิ:[1,2,3]
this is an example
["this","is","an","example"]
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,']'));
- ดูสิ่งนี้สำหรับตำแหน่ง string-splitter ที่ปลอดภัย ( zero-based ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
ในโพสต์นี้ฉันทดสอบวิธีการต่างๆและพบว่าOPENJSON
มันเร็วจริงๆ เร็วกว่าวิธี "delimitedSplit8k ()" ที่โด่งดังมาก ...
เราสามารถใช้อาร์เรย์ภายในอาร์เรย์[[]]
ง่ายๆโดยการใช้สองเท่า สิ่งนี้อนุญาตให้พิมพ์WITH
--clause:
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
ส่วนสามารถจัดการกับเรื่องนี้ได้เช่นกัน ... แต่หลังจากที่พวกเขาไปแล้ว (เปลี่ยนเป็นหนีtext()
โดยปริยาย) ฉันไม่ชอบเวทมนตร์ภายใต้ประทุนดังนั้นฉันจึงชอบ(SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
วิธีการ สิ่งนี้ดูสะอาดสำหรับฉันและเกิดขึ้นต่อไป ... (เพิ่มเติมเกี่ยวกับ CDATA และ XML )
นี่คือ UDF ที่จะทำ มันจะคืนค่าตารางของค่าที่คั่นด้วย, ยังไม่ได้ลองทุกสถานการณ์ แต่ตัวอย่างของคุณใช้ได้ดี
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
คุณจะเรียกมันว่า:
Select * From SplitString('Hello John Smith',' ')
แก้ไข: โซลูชันที่อัปเดตเพื่อจัดการตัวคั่นด้วย len> 1 เช่นเดียวกับใน:
select * From SplitString('Hello**John**Smith','**')
ที่นี่ฉันโพสต์วิธีแก้ปัญหาง่ายๆ
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
ดำเนินการฟังก์ชั่นเช่นนี้
select * from dbo.split('Hello John Smith',' ')
ในความคิดของฉันพวกคุณกำลังทำให้มันซับซ้อนเกินไป เพียงแค่สร้าง CLR UDF และทำได้
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()));
}
};
สิ่งที่เกี่ยวกับการใช้string
และvalues()
คำสั่ง?
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
บรรลุผลสำเร็จ
id item
1 Hello
2 John
3 Smith
ฉันใช้คำตอบของ frederic แต่สิ่งนี้ไม่ทำงานใน SQL Server 2005
ฉันแก้ไขมันและฉันใช้select
กับunion all
และใช้งานได้
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
และชุดผลลัพธ์คือ:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
เนื่องจากการที่ EXEC
เรียกโพรซีเดอร์ที่เก็บไว้แบบ implicitly และคุณไม่สามารถใช้โพรซีเดอร์ที่เก็บใน UDF ได้
รูปแบบนี้ใช้งานได้ดีและคุณสามารถพูดคุยทั่วไป
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
ทราบFIELD , INDEXและประเภท
ปล่อยให้ตารางที่มีตัวระบุเช่น
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
จากนั้นคุณสามารถเขียน
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
แยกและหล่อชิ้นส่วนทั้งหมด
หากฐานข้อมูลของคุณมีระดับความเข้ากันได้ 130 หรือสูงกว่าคุณสามารถใช้ฟังก์ชันSTRING_SPLITพร้อมกับคำสั่งOFFSET FETCHเพื่อรับรายการเฉพาะตามดัชนี
ในการรับไอเท็มที่ดัชนี N (ขึ้นอยู่กับศูนย์) คุณสามารถใช้รหัสต่อไปนี้
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
ในการตรวจสอบระดับความเข้ากันได้ของฐานข้อมูลของคุณให้เรียกใช้รหัสนี้:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
วิธีการตาม -split เนื่องจากช่วยให้สามารถดึงค่าประเภทที่ปลอดภัยและไม่จำเป็นต้องมีแบบสอบถามย่อย แต่นี่เป็น สิ่งที่ดี. +1 จากด้านของฉัน
STRING_SPLIT
ความต้องการสำหรับ v2016 + ในกรณีนี้มันเป็นเรื่องดีที่จะใช้หรือOPENJSON
JSON_VALUE
คุณอาจต้องการตรวจสอบคำตอบของฉัน
ฉันกำลังมองหาทางออกบนเน็ตและด้านล่างใช้ได้สำหรับฉัน อ้าง
และคุณเรียกใช้ฟังก์ชันดังนี้:
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
อีกส่วนหนึ่งได้รับส่วนหนึ่งของสตริงโดยฟังก์ชั่น 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
และการใช้งาน:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
ซึ่งผลตอบแทน:
c
ลองสิ่งนี้:
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
ทดสอบแบบนี้:
select * from SplitWordList('Hello John Smith')
ตัวอย่างต่อไปนี้ใช้ CTE แบบเรียกซ้ำ
อัปเดต 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
การสาธิตเกี่ยวกับ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 โดยไม่จำเป็นต้องมีฟังก์ชั่น:
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);
หากคุณต้องการสนับสนุนสตริงที่กำหนดเอง (ที่มีอักขระพิเศษ 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);
ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันคิดว่าบางคนสามารถได้ประโยชน์จากโซลูชันของฉัน
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
ข้อดี:
ข้อ จำกัด :
หมายเหตุ : วิธีแก้ปัญหาสามารถมอบสตริงย่อยได้สูงสุด N
เพื่อเอาชนะข้อ จำกัด ที่เราสามารถใช้ต่อไปโทษ
แต่วิธีการแก้ปัญหาข้างต้นไม่สามารถใช้ในตารางได้อีก (Actaully i ไม่สามารถใช้งานได้)
ฉันหวังว่าโซลูชันนี้จะช่วยได้บ้าง
อัปเดต:ในกรณีที่มีการบันทึก> 50000 ไม่แนะนำให้ใช้LOOPS
เนื่องจากจะทำให้ประสิทธิภาพลดลง
วิธีการแก้ปัญหาการติดตั้งตามเพียวใช้TVF
กับ CTE
recursive คุณสามารถJOIN
และAPPLY
ฟังก์ชั่นนี้ไปยังชุดข้อมูลใด ๆ
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
การใช้งาน:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
ผลลัพธ์:
value index
-------------
John 1
คำตอบอื่น ๆ เกือบทั้งหมดกำลังแทนที่สายอักขระที่ถูกแยกซึ่งทำให้สิ้นเปลืองรอบ CPU และทำการจัดสรรหน่วยความจำที่ไม่จำเป็น
ฉันครอบคลุมวิธีที่ดีกว่าในการแยกสตริงที่นี่: http://www.digitalruby.com/split-string-sql-server/
นี่คือรหัส:
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.
โซลูชัน CTE แบบเรียกซ้ำพร้อมกับความเจ็บปวดของเซิร์ฟเวอร์ทดสอบ
การติดตั้ง Schema MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
แบบสอบถาม 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
ผลลัพธ์ :
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
ในขณะที่คล้ายกับคำตอบจาก xml โดย josejuan ฉันพบว่าการประมวลผลเส้นทาง xml เพียงครั้งเดียวจากนั้นการหมุนรอบมีประสิทธิภาพปานกลาง:
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
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
วิ่งใน 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
และใช้มัน
select *from dbo.fnSplitString('Querying SQL Server','')
หากใครต้องการได้รับข้อความที่แยกจากกันเพียงส่วนหนึ่งสามารถใช้สิ่งนี้
เลือก * จาก 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
)
ฉัน devoloped นี้
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
สิ่งเดียวที่คุณควรใส่ใจคือจุด '.' จุดสิ้นสุดของ @x นั้นควรอยู่ที่นั่นเสมอ
การสร้างโซลูชัน @NothingsImossible หรือแทนที่จะแสดงความคิดเห็นต่อคำตอบที่ได้รับการโหวตมากที่สุด (ด้านล่างของคำตอบที่ยอมรับ) ฉันพบว่ารวดเร็วและสกปรกดังต่อไปนี้โซลูชันที่ตอบสนองความต้องการของฉันเอง
รับสาย "ก่อน; ที่สอง; ที่สาม; ที่สี่; ที่ห้า" พูดว่าฉันต้องการที่จะได้รับโทเค็นที่สาม มันจะทำงานได้ก็ต่อเมื่อเรารู้ว่ามีกี่โทเค็นสตริงที่จะมี - ในกรณีนี้คือ 5. ดังนั้นวิธีการของฉันคือการตัดโทเค็นสองอันสุดท้ายออกไป (เคียวรีด้านใน) แล้วสับโทเค็นสองอันแรก แบบสอบถามด้านนอก)
ฉันรู้ว่านี่น่าเกลียดและครอบคลุมเงื่อนไขเฉพาะที่ฉันอยู่ แต่กำลังโพสต์ไว้ในกรณีที่มีคนพบว่ามีประโยชน์ ไชโย
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
declare @strng varchar(max)='hello john smith'
select (
substring(
@strng,
charindex(' ', @strng) + 1,
(
(charindex(' ', @strng, charindex(' ', @strng) + 1))
- charindex(' ',@strng)
)
))
เริ่มต้นด้วยSQL Server 2016เราstring_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')