Sử dụng SQL Server, làm cách nào để tách một chuỗi để tôi có thể truy cập mục x?
Lấy một chuỗi "Xin chào John Smith". Làm cách nào tôi có thể chia chuỗi theo khoảng trắng và truy cập vào mục tại chỉ mục 1 sẽ trả về "John"?
Sử dụng SQL Server, làm cách nào để tách một chuỗi để tôi có thể truy cập mục x?
Lấy một chuỗi "Xin chào John Smith". Làm cách nào tôi có thể chia chuỗi theo khoảng trắng và truy cập vào mục tại chỉ mục 1 sẽ trả về "John"?
Câu trả lời:
Bạn có thể tìm thấy giải pháp trong Hàm do người dùng xác định SQL để phân tích chuỗi được phân tách hữu ích (từ Dự án mã ).
Bạn có thể sử dụng logic đơn giản này:
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)))
và không SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
sẽ phân tách một chuỗi và trả về kết quả bảng một cột mà bạn có thể sử dụng trong SELECT
câu lệnh hoặc ở nơi khác.
Tôi không tin SQL Server có chức năng phân tách tích hợp, vì vậy ngoài UDF, câu trả lời duy nhất khác mà tôi biết là chiếm quyền điều khiển PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME lấy một chuỗi và chia nó trên ký tự dấu chấm. Nó lấy một số làm đối số thứ hai của nó và số đó chỉ định đoạn nào của chuỗi sẽ trả về (làm việc từ sau ra trước).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Vấn đề rõ ràng là khi chuỗi đã chứa một khoảng thời gian. Tôi vẫn nghĩ sử dụng UDF là cách tốt nhất ... có đề xuất nào khác không?
SPLIT()
chức năng không được cung cấp vì nó khuyến khích thiết kế cơ sở dữ liệu kém và cơ sở dữ liệu sẽ không bao giờ được tối ưu hóa để sử dụng dữ liệu được lưu trữ ở định dạng này. RDBMS không bắt buộc phải giúp các nhà phát triển làm những điều ngu ngốc mà nó được thiết kế để không xử lý. Câu trả lời đúng sẽ luôn là "Bình thường hóa cơ sở dữ liệu của bạn như chúng tôi đã nói với bạn 40 năm trước." Cả SQL và RDBMS đều không đổ lỗi cho thiết kế kém.
Đầu tiên, tạo một hàm (sử dụng CTE, biểu thức bảng chung không cần dùng bảng tạm thời)
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
Sau đó, sử dụng nó như bất kỳ bảng nào (hoặc sửa đổi nó để phù hợp với Proc được lưu trữ hiện tại của bạn) như thế này.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Cập nhật
Phiên bản trước sẽ thất bại cho chuỗi đầu vào dài hơn 4000 ký tự. Phiên bản này đảm nhận giới hạn:
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
Cách sử dụng vẫn như cũ.
100
(để ngăn chặn vòng lặp vô hạn). Sử dụng gợi ý MAXRECURSION để xác định số mức đệ quy ( 0
đến 32767
, 0
là "không giới hạn" - có thể đè bẹp máy chủ). BTW, câu trả lời tốt hơn nhiều PARSENAME
, bởi vì nó phổ quát :-). +1
maxrecursion
vào giải pháp này hãy ghi nhớ câu hỏi này và câu trả lời của nó Cách thiết lập maxrecursion
tùy chọn cho CTE bên trong Hàm giá trị bảng .
s
không còn được xác định
Hầu hết các giải pháp ở đây sử dụng trong khi các vòng lặp hoặc CTE đệ quy. Một cách tiếp cận dựa trên tập hợp sẽ tốt hơn, tôi hứa, nếu bạn có thể sử dụng một dấu phân cách khác với khoảng trắng:
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
);
Sử dụng mẫu:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Các kết quả:
----
blat
Bạn cũng có thể thêm idx
bạn muốn làm đối số cho hàm, nhưng tôi sẽ để nó như một bài tập cho người đọc.
Bạn không thể làm điều này chỉ với chức năng gốcSTRING_SPLIT
được thêm vào trong SQL Server 2016, vì không có gì đảm bảo rằng đầu ra sẽ được hiển thị theo thứ tự của danh sách gốc. Nói cách khác, nếu bạn vượt qua trong 3,6,1
kết quả có thể sẽ theo thứ tự đó, nhưng nó có thể 1,3,6
. Tôi đã yêu cầu sự giúp đỡ của cộng đồng trong việc cải thiện chức năng tích hợp ở đây:
Với đủ phản hồi định tính , họ thực sự có thể xem xét thực hiện một số cải tiến sau:
Thông tin thêm về các hàm phân tách, tại sao (và chứng minh điều đó) trong khi các vòng lặp và CTE đệ quy không mở rộng và các lựa chọn thay thế tốt hơn, nếu tách chuỗi đến từ lớp ứng dụng:
Tuy nhiên, trên SQL Server 2016 trở lên, bạn nên xem STRING_SPLIT()
và STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
và đầu ra được tạo ra là: Giá trị Xin chào ello llo lo o John ohn hn n smith mith ith th h
Bạn có thể tận dụng bảng Số để thực hiện phân tích chuỗi.
Tạo bảng số vật lý:
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
Tạo bảng thử nghiệm với 1000000 hàng
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
Tạo chức năng
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
Cách sử dụng (xuất ra hàng 3 triệu trong 40 giây trên máy tính xách tay của tôi)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
dọn dẹp
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
Hiệu suất ở đây không đáng kinh ngạc, nhưng gọi một hàm trên một triệu bảng hàng không phải là ý tưởng tốt nhất. Nếu thực hiện một chuỗi phân chia trên nhiều hàng tôi sẽ tránh chức năng.
desc
loại bỏ?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
từ @NothingsImpossible hoàn thành sau 1,5 phút. @hello_earth Giải pháp của bạn sẽ so sánh như thế nào trên các chuỗi dài hơn với hơn 4 trường?
Câu hỏi này không phải là về cách tiếp cận phân tách chuỗi , mà là về cách lấy phần tử thứ n .
Tất cả các câu trả lời ở đây đang làm một số loại tách chuỗi sử dụng đệ quy, CTE
s, nhiều CHARINDEX
, REVERSE
và PATINDEX
, chức năng phát minh, kêu gọi các phương pháp CLR, bảng số, CROSS APPLY
s ... Hầu hết các câu trả lời bao gồm nhiều dòng mã.
Nhưng - nếu bạn thực sự không muốn gì hơn là một cách tiếp cận để có được phần tử thứ n - thì điều này có thể được thực hiện như một lớp lót thực sự , không UDF, thậm chí không phải là một lựa chọn phụ ... Và như một lợi ích bổ sung: gõ an toàn
Nhận phần 2 giới hạn bởi một khoảng trắng:
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)')
Tất nhiên, bạn có thể sử dụng các biến cho dấu phân cách và vị trí (sử dụng sql:column
để truy xuất vị trí trực tiếp từ giá trị của truy vấ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)')
Nếu chuỗi của bạn có thể bao gồm các ký tự bị cấm (đặc biệt là một trong số &><
), bạn vẫn có thể làm theo cách này. Trước tiên, chỉ cần sử dụng FOR XML PATH
chuỗi của bạn để thay thế tất cả các ký tự bị cấm bằng chuỗi thoát phù hợp.
Đó là một trường hợp rất đặc biệt nếu - ngoài ra - dấu phân cách của bạn là dấu chấm phẩy . Trong trường hợp này, trước tiên tôi thay thế dấu phân cách thành '# DLMT #' và cuối cùng thay thế nó thành các thẻ 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)');
Rất tiếc, các nhà phát triển đã quên trả lại chỉ mục của phần này STRING_SPLIT
. Nhưng, sử dụng SQL-Server 2016+, có JSON_VALUE
và OPENJSON
.
Với JSON_VALUE
chúng ta có thể vượt qua ở vị trí như mảng của chỉ mục.
Đối với OPENJSON
các tài liệu nêu rõ:
Khi OPENJSON phân tích một mảng JSON, hàm sẽ trả về các chỉ mục của các thành phần trong văn bản JSON dưới dạng các khóa.
Một chuỗi như 1,2,3
không cần gì nhiều hơn ngoặc : [1,2,3]
.
Một chuỗi các từ như this is an example
cần phải được ["this","is","an","example"]
.
Đây là những hoạt động chuỗi rất dễ dàng. Hãy thử nó:
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,']'));
- Xem cái này cho bộ tách chuỗi an toàn vị trí ( dựa trên zero ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
Trong bài viết này tôi đã thử nghiệm các cách tiếp cận khác nhau và tìm thấy, điều đó OPENJSON
thực sự nhanh chóng. Thậm chí nhanh hơn nhiều so với phương thức "deliatedSplit8k ()" nổi tiếng ...
Chúng ta có thể sử dụng một mảng trong một mảng chỉ bằng cách sử dụng gấp đôi [[]]
. Điều này cho phép gõ WITH
-clided:
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
phần cũng có thể đối phó với điều này ... Nhưng sau khi các diễn viên họ biến mất (thay đổi thành thoát text()
hoàn toàn). Tôi không thích phép thuật dưới mui xe , vì vậy tôi thích (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
cách tiếp cận -. Điều này có vẻ sạch hơn đối với tôi và dù sao cũng xảy ra ... (Một số thông tin khác về CDATA và XML ).
Đây là một UDF sẽ làm điều đó. Nó sẽ trả về một bảng các giá trị được phân tách, chưa thử tất cả các kịch bản trên đó nhưng ví dụ của bạn hoạt động tốt.
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
Bạn sẽ gọi nó như thế này:
Select * From SplitString('Hello John Smith',' ')
Chỉnh sửa: Giải pháp cập nhật để xử lý các dấu phân cách với len> 1 như trong:
select * From SplitString('Hello**John**Smith','**')
Ở đây tôi đăng một cách đơn giản của giải pháp
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
Thực hiện chức năng như thế này
select * from dbo.split('Hello John Smith',' ')
Theo ý kiến của tôi, các bạn đang làm cho nó quá phức tạp. Chỉ cần tạo một UDF CLR và được thực hiện với nó.
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()));
}
};
Điều gì về việc sử dụng string
và values()
tuyên bố?
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
Kết quả đạt được.
id item
1 Hello
2 John
3 Smith
Tôi sử dụng câu trả lời của frederic nhưng điều này không hoạt động trong SQL Server 2005
Tôi sửa đổi nó và tôi đang sử dụng select
với union all
và nó hoạt động
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
Và tập kết quả là:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
ngầm gọi một thủ tục được lưu trữ và bạn không thể sử dụng các thủ tục được lưu trữ trong UDF.
Mẫu này hoạt động tốt và bạn có thể khái quát
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
lưu ý FIELD , INDEX và TYPE .
Hãy để một số bảng với định danh như
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Sau đó, bạn có thể viết
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ách và đúc tất cả các bộ phận.
Nếu cơ sở dữ liệu của bạn có mức độ tương thích từ 130 trở lên thì bạn có thể sử dụng hàm STRINGinksLIT cùng với các mệnh đề OFFSET FETCH để lấy mục cụ thể theo chỉ mục.
Để lấy mục tại chỉ mục N (không dựa trên), bạn có thể sử dụng mã sau
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Để kiểm tra mức độ tương thích của cơ sở dữ liệu của bạn , hãy thực thi mã này:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
cách tiếp cận dựa trên -split, vì nó cho phép tìm nạp loại giá trị an toàn và không cần truy vấn phụ, nhưng đây là một truy vấn phụ tốt một. +1 từ phía tôi
STRING_SPLIT
nhu cầu cho v2016 +. Trong trường hợp này tốt hơn là sử dụng OPENJSON
hoặc JSON_VALUE
. Bạn có thể muốn kiểm tra câu trả lời của tôi
Tôi đã tìm kiếm giải pháp trên mạng và các công việc dưới đây cho tôi. Ref .
Và bạn gọi hàm như thế này:
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
Một phần khác nhận được phần thứ nhất của chuỗi bằng hàm delimet:
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
và cách sử dụng:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
Trả về:
c
Thử cái này:
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
Kiểm tra nó như thế này:
select * from SplitWordList('Hello John Smith')
Ví dụ sau sử dụng CTE đệ quy
Cập nhật ngày 18 tháng 9 năm 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
Bản trình diễn trên 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
Bạn có thể tách một chuỗi trong SQL mà không cần một hàm:
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);
Nếu bạn cần hỗ trợ các chuỗi tùy ý (với các ký tự đặc biệt 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);
Tôi biết đó là một Câu hỏi cũ, nhưng tôi nghĩ ai đó có thể hưởng lợi từ giải pháp của tôi.
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
Ưu điểm:
Hạn chế:
Lưu ý : giải pháp có thể cung cấp chuỗi con tối đa cho N.
Để vượt qua giới hạn chúng ta có thể sử dụng sau đây ref .
Nhưng một lần nữa giải pháp trên không thể được sử dụng trong một bảng (Actaully tôi không thể sử dụng nó).
Một lần nữa tôi hy vọng giải pháp này có thể giúp một số người.
Cập nhật: Trong trường hợp Bản ghi> 50000 không nên sử dụng LOOPS
vì nó sẽ làm giảm Hiệu suất
Giải pháp dựa trên tập hợp thuần túy sử dụng TVF
với đệ quy CTE
. Bạn có thể JOIN
và APPLY
chức năng này cho bất kỳ tập dữ liệu.
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
Sử dụng:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Kết quả:
value index
-------------
John 1
Hầu như tất cả các câu trả lời khác đang thay thế chuỗi bị chia tách gây lãng phí chu kỳ CPU và thực hiện phân bổ bộ nhớ không cần thiết.
Tôi trình bày một cách tốt hơn nhiều để thực hiện phân tách chuỗi ở đây: http://www.digitalruby.com/split-opes-sql-server/
Đây là mã:
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.
Giải pháp CTE đệ quy với đau máy chủ, kiểm tra nó
Thiết lập lược đồ MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Truy vấn 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
Kết quả :
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
Mặc dù tương tự như câu trả lời dựa trên xml của josejuan, tôi thấy rằng việc xử lý đường dẫn xml chỉ một lần, sau đó xoay vòng hiệu quả hơn vừa phải:
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
chạy trong 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
chạy trong 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
VÀ SỬ DỤNG CNTT
select *from dbo.fnSplitString('Querying SQL Server','')
nếu bất cứ ai muốn chỉ có một phần của văn bản tách biệt có thể sử dụng
chọn * từ 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
)
Tôi đã phá hủy điều này,
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
sự chú ý duy nhất bạn nên là dấu chấm '.' kết thúc của @x luôn luôn ở đó.
dựa trên giải pháp @NothingsImpossible, hoặc, đúng hơn, nhận xét về câu trả lời được bình chọn nhiều nhất (ngay dưới câu trả lời được chấp nhận), tôi đã tìm thấy cách nhanh chóng và bẩn thỉu sau đây giải pháp đáp ứng nhu cầu của riêng tôi - nó có lợi ích duy nhất trong miền SQL.
đưa ra một chuỗi "thứ nhất, thứ hai, thứ ba, thứ tư, thứ năm", giả sử, tôi muốn nhận được mã thông báo thứ ba. điều này chỉ hoạt động nếu chúng ta biết chuỗi sẽ có bao nhiêu mã thông báo - trong trường hợp này là 5. vì vậy cách hành động của tôi là cắt hai mã thông báo cuối cùng (truy vấn bên trong), và sau đó cắt hai mã thông báo đầu tiên đi ( truy vấn bên ngoài)
Tôi biết rằng điều này là xấu và bao gồm các điều kiện cụ thể tôi đã có, nhưng tôi đang đăng nó chỉ trong trường hợp ai đó thấy nó hữu ích. chúc mừng
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)
)
))
Bắt đầu với SQL Server 2016, chúng tôi string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
không đảm bảo trả lại cùng một thứ tự. Nhưng OPENJSON
có (xem câu trả lời của tôi (phần cập nhật) )