Chuỗi phân tách T-SQL


139

Tôi có một cột SQL Server 2008 R2 chứa một chuỗi mà tôi cần phải phân tách bằng dấu phẩy. Tôi đã thấy nhiều câu trả lời trên StackOverflow nhưng không có câu trả lời nào hoạt động trong R2. Tôi đã đảm bảo rằng tôi đã chọn quyền trên bất kỳ ví dụ chức năng phân chia nào. Bất kỳ trợ giúp đánh giá rất cao.


7
Đây là một trong hàng triệu câu trả lời mà tôi thích stackoverflow.com/a/1846561/227755
Nurettin

2
Bạn có nghĩa là "không ai trong số họ làm việc"? Bạn có thể cụ thể hơn không?
Aaron Bertrand

Andy đã chỉ cho tôi đi đúng hướng khi tôi đang thực hiện chức năng không chính xác. Đây là lý do tại sao không có câu trả lời ngăn xếp khác làm việc. Lỗi của tôi.
Lee Grindon

2
có thể trùng lặp chuỗi Split trong SQL
Luv

Có một mdq.RegexSplitchức năng trong tiện ích bổ sung "Dịch vụ dữ liệu chủ", có thể giúp ích. Chắc chắn đáng để điều tra .
jpaugh

Câu trả lời:


233

Tôi đã sử dụng SQL này trước đây có thể phù hợp với bạn: -

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

và sử dụng nó: -

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')

1
Thật tuyệt, đây chính xác là những gì tôi đang tìm kiếm cảm ơn rất nhiều
Lee Grindon

2
Cảm ơn rất nhiều Andy. Tôi đã thực hiện một cải tiến nhỏ cho tập lệnh của bạn để cho phép hàm trả về một mục tại một chỉ mục cụ thể trong chuỗi phân tách. Nó chỉ hữu ích trong các tình huống khi cấu trúc của cột được phân tích cú pháp. gist.github.com/klimaye/8147193
CF_Maintainer

1
Tôi đã đăng một số cải tiến (với các trường hợp kiểm tra sao lưu) lên trang github của tôi ở đây . Tôi sẽ đăng nó dưới dạng câu trả lời trong chuỗi Stack Overflow này khi tôi có đủ đại diện để vượt quá bài "bảo vệ"
mpag

8
Mặc dù đây là một câu trả lời tuyệt vời, nhưng nó đã lỗi thời ... Các cách tiếp cận theo thủ tục (đặc biệt là các vòng lặp) là điều cần tránh ... Thật đáng để xem xét các câu trả lời mới hơn ...
Shnugo

2
Tôi hoàn toàn đồng ý với @Shnugo. Bộ chia vòng lặp hoạt động nhưng chậm khủng khiếp. Một cái gì đó như sqlservercentral.com/articles/Tally+Table/72993 tốt hơn nhiều. Một số tùy chọn dựa trên bộ tuyệt vời khác có thể được tìm thấy ở đây. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

61

Thay vì CTE đệ quy và trong khi các vòng lặp, có ai đã xem xét một cách tiếp cận dựa trên tập hợp hơn chưa? Lưu ý rằng chức năng này được viết cho câu hỏi, dựa trên SQL Server 2008 và dấu phẩy là dấu phân cách . Trong SQL Server 2016 trở lên (và ở mức độ tương thích 130 trở lên), STRING_SPLIT()là một lựa chọn tốt hơn .

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE
AS
  RETURN ( SELECT [Value] FROM 
  ( 
    SELECT [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
      CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
    FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
      FROM sys.all_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

Nếu bạn muốn tránh giới hạn độ dài của chuỗi là <= số lượng hàng trong sys.all_columns(9,980 modeltrong SQL Server 2017; cao hơn nhiều trong cơ sở dữ liệu người dùng của riêng bạn), bạn có thể sử dụng các cách tiếp cận khác để lấy số, chẳng hạn như xây dựng bảng số của riêng bạn . Bạn cũng có thể sử dụng CTE đệ quy trong trường hợp bạn không thể sử dụng bảng hệ thống hoặc tạo bảng của riêng mình:

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

Nhưng bạn sẽ phải nối OPTION (MAXRECURSION 0)(hoặc MAXRECURSION <longest possible string length if < 32768>) vào truy vấn bên ngoài để tránh lỗi với đệ quy cho chuỗi> 100 ký tự. Nếu đó cũng không phải là một lựa chọn tốt thì hãy xem câu trả lời này như được chỉ ra trong các bình luận.

(Ngoài ra, dấu phân cách sẽ phải NCHAR(<=1228). Vẫn đang nghiên cứu tại sao.)

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:


1
Có một lỗi nhỏ trong quy trình này đối với trường hợp sẽ có giá trị null ở cuối chuỗi - chẳng hạn như trong '1,2, 4,' - vì giá trị cuối cùng không được phân tích cú pháp. Để sửa lỗi này, nên thay thế biểu thức "Số WHERE <= LEN (@List)" bằng "Số WHERE <= LEN (@List) + 1".
SylvainL

@SylvainL Tôi đoán điều đó phụ thuộc vào hành vi bạn muốn. Theo kinh nghiệm của tôi, hầu hết mọi người muốn bỏ qua bất kỳ dấu phẩy nào vì chúng không thực sự đại diện cho một phần tử thực (bạn cần bao nhiêu bản sao của một chuỗi trống)? Dù sao, cách thực sự để làm điều này - nếu bạn sẽ theo liên kết thứ hai - là bước đi loanh quanh với việc tách các chuỗi lớn xấu xí trong T-SQL chậm.
Aaron Bertrand

1
Giống như bạn đã nói, hầu hết mọi người muốn bỏ qua bất kỳ dấu phẩy nào nhưng than ôi, không phải tất cả. Tôi cho rằng một giải pháp hoàn chỉnh hơn sẽ là thêm một tham số để chỉ định những việc cần làm trong trường hợp này nhưng nhận xét của tôi chỉ là một lưu ý nhỏ để đảm bảo rằng không ai quên về khả năng này, vì nó có thể khá thực trong nhiều trường hợp.
SylvainL

Tôi có một hành vi kỳ lạ với chức năng đó. Nếu tôi sử dụng trực tiếp một chuỗi làm tham số - nó hoạt động. Nếu tôi có một varchar, nó không. Bạn có thể sao chép dễ dàng: khai báo invarchar dưới dạng varchar set invarchar = 'ta; aa; qq' CHỌN Giá trị từ [dbo]. [SplitString] (invarchar, ';') CHỌN Giá trị từ [dbo]. [SplitString] ('ta; aa; qq ','; ')
Patrick Desjardins

Tôi thích cách tiếp cận này, nhưng nếu số lượng đối tượng được trả về sys.all_objectsít hơn số lượng ký tự trong chuỗi đầu vào thì nó sẽ cắt bớt chuỗi và các giá trị sẽ bị mất. Vì sys.all_objectschỉ được sử dụng như một chút hack để tạo hàng, nên có nhiều cách tốt hơn để làm điều này, ví dụ câu trả lời này .
đốt ngón tay

56

Cuối cùng, sự chờ đợi đã kết thúc trong SQL Server 2016, họ đã giới thiệu chức năng Chia chuỗi:STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

Tất cả các phương thức khác để phân tách chuỗi như XML, bảng Tally, vòng lặp, v.v. đã bị STRING_SPLIThàm này thổi bay .

Đây là một bài viết tuyệt vời với so sánh hiệu suất: Những bất ngờ về hiệu suất và Giả định: STRINGinksLIT


5
rõ ràng là trả lời câu hỏi làm thế nào để phân tách chuỗi cho những người có máy chủ được cập nhật, nhưng những người trong chúng ta vẫn bị mắc kẹt trong năm 2008 / 2008R2, sẽ phải đi với một trong những câu trả lời khác ở đây.
mpag

2
Bạn cần xem mức độ tương thích trong cơ sở dữ liệu của bạn. Nếu nó thấp hơn 130, bạn sẽ không thể sử dụng chức năng STRINGinksLIT.
Luis Teijon

Trên thực tế, nếu khả năng tương thích không phải là 130 và bạn đang chạy 2016 (hoặc Azure SQL), bạn có thể đặt khả năng tương thích lên tới 130 bằng cách sử dụng: ALTER DATABASE DatabaseName SET
COMPATITALITY_LEVEL

23

Cách dễ nhất để làm điều này là bằng cách sử dụng XMLđịnh dạng.

1. Chuyển đổi chuỗi thành hàng không có bảng

TRUY VẤN

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

KẾT QUẢ

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. Chuyển đổi thành các hàng từ một bảng có ID cho mỗi hàng CSV

BẢNG NGUỒN

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

TRUY VẤN

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

KẾT QUẢ

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x

Cách tiếp cận này sẽ bị phá vỡ nếu @Stringchứa các ký tự bị cấm ... Tôi chỉ đăng một câu trả lời để khắc phục vấn đề này.
Shnugo

9

Tôi cần một cách nhanh chóng để thoát khỏi +4từ một zip code .

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

Không có Proc ... không UDF ... chỉ cần một lệnh nội tuyến nhỏ gọn thực hiện những gì nó phải. Không cầu kỳ, không thanh lịch.

Thay đổi dấu phân cách khi cần, v.v., và nó sẽ hoạt động cho mọi thứ.


4
Đây không phải là những gì câu hỏi về. OP có giá trị như '234,542,23' và họ muốn chia nó thành ba hàng ... Hàng thứ nhất: 234, hàng thứ 2: 542, hàng thứ 3: 23. Đó là một điều khó khăn trong SQL.
codeulike

7

nếu bạn thay thế

WHILE CHARINDEX(',', @stringToSplit) > 0

với

WHILE LEN(@stringToSplit) > 0

bạn có thể loại bỏ lần chèn cuối cùng đó sau vòng lặp while!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END

Điều này sẽ dẫn đến ký tự cuối cùng của phần tử cuối cùng bị cắt ngắn. tức là "AL, AL" sẽ trở thành "AL" | "A" tức là "ABC, ABC, ABC" sẽ trở thành "ABC" | "ABC" | "AB"
Nhà phát triển Microsoft

phụ thêm +1để SELECT @pos = LEN(@stringToSplit)xuất hiện để giải quyết vấn đề đó. Tuy nhiên, SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)sẽ trả về Invalid length parameter passed to the LEFT or SUBSTRING functiontrừ khi bạn thêm +1vào tham số thứ ba của SUBSTRING. hoặc bạn có thể thay thế nhiệm vụ đó bằngSET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag

1
Tôi đã đăng một số cải tiến (với các trường hợp kiểm tra sao lưu) lên trang github của tôi ở đây . Tôi sẽ đăng nó dưới dạng câu trả lời trong chuỗi Stack Overflow này khi tôi có đủ đại diện để vượt quá bài "bảo vệ"
mpag

Tôi cũng đã lưu ý vấn đề được chỉ ra bởi Terry ở trên. Nhưng logic được đưa ra bởi @AviG tuyệt vời đến mức nó không thất bại ở giữa cho một danh sách dài các mã thông báo. Hãy thử cuộc gọi thử nghiệm này để xác minh (Cuộc gọi này sẽ trả về 969 mã thông báo) chọn * từ dbo.split chuỗi ('token1, token2 ,,,,,,,, token969') Sau đó, tôi đã thử mã được cung cấp bởi mpag để kiểm tra kết quả cho cùng gọi ở trên và thấy nó chỉ có thể trả lại 365 mã thông báo. Cuối cùng tôi đã sửa mã bằng AviG ở trên và đăng chức năng không có lỗi dưới dạng trả lời mới bên dưới vì nhận xét ở đây chỉ cho phép văn bản giới hạn. Kiểm tra trả lời dưới tên của tôi để thử nó.
Gemunu R Wickremasinghe

3

Tất cả các hàm để tách chuỗi sử dụng một số loại Loop-ing (lặp) có hiệu suất kém. Chúng nên được thay thế bằng giải pháp dựa trên thiết lập.

Mã này thực thi tuyệt vời.

CREATE FUNCTION dbo.SplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO

Cách tiếp cận này sẽ bị phá vỡ nếu @Listchứa các ký tự bị cấm ... Tôi chỉ đăng một câu trả lời để khắc phục vấn đề này.
Shnugo

Tôi nâng cao phản hồi của bạn vì bạn làm việc với không gian là người phân định và người được bình chọn cao nhất không
KMC

3

Cách tiếp cận thường được sử dụng với các phần tử XML bị phá vỡ trong trường hợp các ký tự bị cấm. Đây là một cách tiếp cận để sử dụng phương pháp này với bất kỳ loại ký tự nào, ngay cả với dấu chấm phẩy là dấu phân cách.

Bí quyết là, đầu tiên sử dụng SELECT SomeString AS [*] FOR XML PATH('')để có được tất cả các nhân vật bị cấm thoát đúng cách. Đó là lý do, tại sao tôi thay thế dấu phân cách thành giá trị ma thuật để tránh những rắc rối với ;tư cách là dấu phân cách.

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

Kết quả

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F

2

Tôi đã phải viết một cái gì đó như thế này gần đây. Đây là giải pháp tôi đã đưa ra. Nó được khái quát hóa cho bất kỳ chuỗi phân cách nào và tôi nghĩ rằng nó sẽ hoạt động tốt hơn một chút:

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END

1

Một giải pháp sử dụng CTE, nếu có ai cần điều đó (ngoài tôi, người rõ ràng đã làm, đó là lý do tại sao tôi viết nó).

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit

1

Điều này được thiết kế hẹp hơn. Khi tôi làm điều này, tôi thường có một danh sách các id duy nhất được phân tách bằng dấu phẩy (INT hoặc BIGINT), mà tôi muốn sử dụng như một bảng để sử dụng làm liên kết bên trong với một bảng khác có khóa chính là INT hoặc BIGINT. Tôi muốn một hàm có giá trị trong bảng được trả về để tôi có phép nối hiệu quả nhất có thể.

Sử dụng mẫu sẽ là:

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

Tôi đã đánh cắp ý tưởng từ http://sqlrecords.blogspot.com/2012/11/converting-deliated-list-to-table.html , thay đổi nó thành giá trị bảng trong dòng và được đặt thành INT.

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO

1

Có một phiên bản chính xác ở đây nhưng tôi nghĩ sẽ tốt hơn nếu thêm một chút khả năng chịu lỗi trong trường hợp chúng có dấu phẩy cũng như làm cho nó để bạn có thể sử dụng nó không phải là một chức năng mà là một phần của một đoạn mã lớn hơn . Chỉ trong trường hợp bạn chỉ sử dụng nó một lần và không cần chức năng. Điều này cũng dành cho số nguyên (là những gì tôi cần nó) vì vậy bạn có thể phải thay đổi loại dữ liệu của mình.

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs

nếu bạn đã SET @StringToSeperate = @StringToSeperate+','ngay lập tức trước WHILEvòng lặp tôi nghĩ bạn có thể loại bỏ khối "thêm giá trị cuối cùng". Xem thêm sol'n của tôi trên github
mpag

Câu trả lời này dựa trên cái gì? Có rất nhiều câu trả lời ở đây, và nó hơi khó hiểu. Cảm ơn.
jpaugh

1

Tôi đã sửa đổi + chức năng của Andy Robinson một chút. Bây giờ bạn chỉ có thể chọn một phần bắt buộc từ bảng trả về:

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5


1

Nếu bạn cần một giải pháp đặc biệt nhanh chóng cho các trường hợp phổ biến với mã tối thiểu, thì hai lớp lót CTE đệ quy này sẽ thực hiện:

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

Sử dụng điều này như một tuyên bố độc lập hoặc chỉ cần thêm các CTE ở trên vào bất kỳ truy vấn nào của bạn và bạn sẽ có thể tham gia bảng kết quả bvới những người khác để sử dụng trong bất kỳ biểu thức nào khác.

chỉnh sửa (bởi Shnugo)

Nếu bạn thêm một bộ đếm, bạn sẽ nhận được một chỉ mục vị trí cùng với Danh sách:

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

Kết quả:

n   s
1   1
2   2333
3   344
4   4

Tôi thích cách tiếp cận này. Tôi hy vọng bạn không phiền, rằng tôi đã thêm một số cải tiến trực tiếp vào câu trả lời của bạn. Chỉ cần cảm thấy thoải mái để chỉnh sửa điều này theo bất kỳ cách thuận tiện nào ...
Shnugo

1

Tôi chọn tuyến đường xml bằng cách gói các giá trị thành các phần tử (M nhưng mọi thứ đều hoạt động):

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)

0

đây là một phiên bản có thể phân chia trên một mẫu bằng cách sử dụng patindex, một bản chuyển thể đơn giản của bài viết ở trên. Tôi đã có một trường hợp tôi cần phải tách một chuỗi chứa nhiều ký tự phân cách.


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

kết quả trông như thế này

chuỗi chuỗib x y z


0

Cá nhân tôi sử dụng chức năng này:

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)

0

Tôi đã phát triển một Bộ chia đôi (Lấy hai ký tự phân tách) theo yêu cầu Tại đây . Có thể có một số giá trị trong luồng này khi thấy nó được tham chiếu nhiều nhất cho các truy vấn liên quan đến tách chuỗi.

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

Sử dụng:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

Cách sử dụng có thể (Nhận giá trị thứ hai của mỗi lần phân tách):

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2

0

Dưới đây là một ví dụ mà bạn có thể sử dụng như hàm hoặc bạn cũng có thể đặt logic tương tự trong thủ tục. --SELECT * từ [dbo] .fn_SplitString;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;

Cách tiếp cận này sẽ bị phá vỡ nếu @vCSVchứa các ký tự bị cấm ... Tôi chỉ đăng một câu trả lời để khắc phục vấn đề này.
Shnugo

0

Một giải pháp dựa trên cte đệ quy

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;

0

Điều này dựa trên câu trả lời của Andy Robertson, tôi cần một dấu phân cách khác với dấu phẩy.

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

Và để sử dụng nó:

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(Đã thử nghiệm trên SQL Server 2008 R2)

EDIT: mã kiểm tra chính xác


0

/ *

Trả lời chuỗi phân tách T-SQL
Dựa trên câu trả lời của Andy RobinsonAviG
Chức năng nâng cao: Hàm LEN không bao gồm dấu cách trong SQL Server
'Tệp' này phải hợp lệ là cả tệp đánh dấu và tệp SQL


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

Các trường hợp kiểm tra: xem URL được tham chiếu là "chức năng nâng cao" ở trên

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

* /


quay trở lại để tôn vinh "Tập tin 'này phải hợp lệ vì cả tập tin đánh dấu và tập tin SQL"
mpag

-1
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END

-1

Bạn có thể sử dụng chức năng này:

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO

-1

Với tất cả sự tôn trọng đối với @AviG, đây là phiên bản miễn phí của chức năng được anh ấy nghĩ ra để trả lại đầy đủ tất cả các mã thông báo.

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END

-3

Cách dễ nhất:

  1. Cài đặt SQL Server 2016
  2. Sử dụng STRINGinksLIT https://msdn.microsoft.com/en-us/l Library / mt684588.aspx

Nó hoạt động ngay cả trong phiên bản thể hiện :).


Đừng quên đặt "Mức độ tương thích" thành SQL Server 2016 (130) - trong phòng quản lý, nhấp chuột phải vào cơ sở dữ liệu, thuộc tính / tùy chọn / mức độ tương thích.
Tomino

1
Bài viết gốc cho SQL 2008 R2. Cài đặt SQL 2016 có thể không phải là một lựa chọn
Shawn Gavett
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.