Chức năng phân chia tương đương trong T-SQL?


128

Tôi đang tìm cách chia '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ...' (được phân cách bằng dấu phẩy) thành một biến bảng hoặc bảng .

Có ai có một hàm trả về từng cái một không?


http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=50648 Một lựa chọn các phương pháp khác nhau
adolf tỏi

1
Erland Sommarskog đã duy trì câu trả lời có thẩm quyền cho câu hỏi này trong 12 năm qua: http://www.sommarskog.se/arrays-in-sql.html Không đáng để sao chép tất cả các tùy chọn ở đây trên StackOverflow, chỉ cần truy cập trang của anh ấy và bạn sẽ học tất cả những gì bạn muốn biết.
Portman

2
Gần đây tôi đã thực hiện một nghiên cứu nhỏ so sánh các cách tiếp cận phổ biến nhất cho vấn đề này, có thể đáng để đọc: sqlperformance.com/2012/07/t-sql-queries/split-stringssqlperformance.com/2012/08/t- truy vấn sql / Nhận
Aaron Bertrand

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

Có vẻ như bạn đã có một vài câu trả lời tốt ở đây; tại sao không đánh dấu một trong số chúng là câu trả lời hoặc mô tả vấn đề của bạn chi tiết hơn nếu nó vẫn chưa được trả lời.
RyanfaeScotland

Câu trả lời:


51

Đây là một giải pháp lỗi thời:

/*
    Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
    @sString nvarchar(2048),
    @cDelimiter nchar(1)
)
RETURNS @tParts TABLE ( part nvarchar(2048) )
AS
BEGIN
    if @sString is null return
    declare @iStart int,
            @iPos int
    if substring( @sString, 1, 1 ) = @cDelimiter 
    begin
        set @iStart = 2
        insert into @tParts
        values( null )
    end
    else 
        set @iStart = 1
    while 1=1
    begin
        set @iPos = charindex( @cDelimiter, @sString, @iStart )
        if @iPos = 0
            set @iPos = len( @sString )+1
        if @iPos - @iStart > 0          
            insert into @tParts
            values  ( substring( @sString, @iStart, @iPos-@iStart ))
        else
            insert into @tParts
            values( null )
        set @iStart = @iPos+1
        if @iStart > len( @sString ) 
            break
    end
    RETURN

END

Trong SQL Server 2008, bạn có thể đạt được điều tương tự với mã .NET. Có thể nó sẽ hoạt động nhanh hơn, nhưng chắc chắn cách tiếp cận này dễ quản lý hơn.


Cảm ơn, tôi cũng muốn biết. Có lỗi ở đây không? Tôi đã viết mã này có lẽ 6 năm trước và nó đã hoạt động tốt kể từ khi nào.
XOR

Tôi đồng ý. Đây là một giải pháp rất tốt khi bạn không muốn (hoặc đơn giản là không thể) tham gia vào việc tạo các tham số loại bảng, đó là trường hợp trong trường hợp của tôi. Các DBA đã khóa tính năng đó và sẽ không cho phép nó. Cảm ơn XOR!
dscarr

DECLARE VarString NVARCHAR (2048) = 'Mike / John / Miko / Matt'; KHAI THÁC CaracString NVARCHAR (1) = '/'; CHỌN * TỪ dbo.FnSplitString (VarString, CaracString)
fernando yevenes

55

Thử cái này

DECLARE @xml xml, @str varchar(100), @delimiter varchar(10)
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET @delimiter = ','
SET @xml = cast(('<X>'+replace(@str, @delimiter, '</X><X>')+'</X>') as xml)
SELECT C.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as X(C)

HOẶC LÀ

DECLARE @str varchar(100), @delimiter varchar(10)
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET @delimiter = ','
;WITH cte AS
(
    SELECT 0 a, 1 b
    UNION ALL
    SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter)
    FROM CTE
    WHERE b > a
)
SELECT SUBSTRING(@str, a,
CASE WHEN b > LEN(@delimiter) 
    THEN b - a - LEN(@delimiter) 
    ELSE LEN(@str) - a + 1 END) value      
FROM cte WHERE a > 0

Nhiều cách khác để làm điều tương tự là ở đây Làm thế nào để phân tách chuỗi phân cách bằng dấu phẩy?


9
Lưu ý cho bất kỳ ai đang tìm kiếm bộ tách chuỗi chung: Giải pháp đầu tiên được đưa ra ở đây không phải là bộ tách chuỗi chung - chỉ an toàn nếu bạn chắc chắn rằng đầu vào sẽ không bao giờ chứa <, >hoặc &(ví dụ: đầu vào là một chuỗi số nguyên). Bất kỳ ba ký tự nào ở trên sẽ khiến bạn gặp lỗi phân tích thay vì kết quả mong đợi.
miroxlav

1
Sự kiện với các vấn đề được đề cập bởi miroxlav (Điều này có thể giải quyết được với một số suy nghĩ), đây chắc chắn là một trong những giải pháp sáng tạo nhất mà tôi đã tìm thấy (Đầu tiên)! Rất đẹp!
thiếu tá

Các dòng SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter)nên thực sự SELECT b, CHARINDEX(@delimiter, @str, b+1) + LEN(@delimiter). Các b + 1 tạo ra một sự khác biệt lớn. Đã thử nghiệm ở đây với không gian là dấu phân cách, không hoạt động mà không sửa lỗi này.
JwJosefy

@miroxlav Ngoài ra, theo kinh nghiệm của tôi, sử dụng XML để phân tách một chuỗi là một đường vòng cực kỳ tốn kém.
gạch dưới

Giải pháp tuyệt vời! Đáng lưu ý rằng người dùng có thể muốn thêm MAXRECURSIONtùy chọn để tách hơn 100 phần, thay thế LENbằng một cái gì đó từ stackoverflow.com/q/2025585 để xử lý khoảng trắng và loại trừ NULLcác hàng cho NULLđầu vào.
Kevinoid

27

Bạn đã gắn thẻ SQL Server 2008 này nhưng khách truy cập trong tương lai cho câu hỏi này (sử dụng SQL Server 2016+) có thể sẽ muốn biết về STRING_SPLIT .

Với chức năng dựng sẵn mới này, giờ bạn có thể sử dụng

SELECT TRY_CAST(value AS INT)
FROM   STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',') 

Một số hạn chế của chức năng này và một số kết quả đầy hứa hẹn của thử nghiệm hiệu suất có trong bài đăng trên blog này của Aaron Bertrand .


13

Điều này giống như .NET, đối với những bạn quen thuộc với chức năng đó:

CREATE FUNCTION dbo.[String.Split]
(
    @Text VARCHAR(MAX),
    @Delimiter VARCHAR(100),
    @Index INT
)
RETURNS VARCHAR(MAX)
AS BEGIN
    DECLARE @A TABLE (ID INT IDENTITY, V VARCHAR(MAX));
    DECLARE @R VARCHAR(MAX);
    WITH CTE AS
    (
    SELECT 0 A, 1 B
    UNION ALL
    SELECT B, CONVERT(INT,CHARINDEX(@Delimiter, @Text, B) + LEN(@Delimiter))
    FROM CTE
    WHERE B > A
    )
    INSERT @A(V)
    SELECT SUBSTRING(@Text,A,CASE WHEN B > LEN(@Delimiter) THEN B-A-LEN(@Delimiter) ELSE LEN(@Text) - A + 1 END) VALUE      
    FROM CTE WHERE A >0

    SELECT      @R
    =           V
    FROM        @A
    WHERE       ID = @Index + 1
    RETURN      @R
END

SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2'

9

đây là chức năng phân chia mà bạn yêu cầu

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('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',')

5
DECLARE
    @InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5'
    , @delimiter varchar(10) = ','

DECLARE @xml AS XML = CAST(('<X>'+REPLACE(@InputString,@delimiter ,'</X><X>')+'</X>') AS XML)
SELECT C.value('.', 'varchar(10)') AS value
FROM @xml.nodes('X') as X(C)

Nguồn của phản hồi này: http://sqlhint.com/sqlserver/how-to/best-split-feft-tsql-delrict


Trong khi về mặt lý thuyết có thể trả lời câu hỏi, tốt hơn là nên bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo.
Xavi López

1
@Xavi: ok, tôi đã bao gồm các phần thiết yếu của câu trả lời. Cảm ơn gợi ý của bạn.
Mihai Bejenariu

3

Tôi bị cám dỗ để ép trong giải pháp yêu thích của tôi. Bảng kết quả sẽ bao gồm 2 cột: PosIdx cho vị trí của số nguyên tìm thấy; và Giá trị bằng số nguyên.


create function FnSplitToTableInt
(
    @param nvarchar(4000)
)
returns table as
return
    with Numbers(Number) as 
    (
        select 1 
        union all 
        select Number + 1 from Numbers where Number < 4000
    ),
    Found as
    (
        select 
            Number as PosIdx,
            convert(int, ltrim(rtrim(convert(nvarchar(4000), 
                substring(@param, Number, 
                charindex(N',' collate Latin1_General_BIN, 
                @param + N',', Number) - Number))))) as Value
        from   
            Numbers 
        where  
            Number <= len(@param)
        and substring(N',' + @param, Number, 1) = N',' collate Latin1_General_BIN
    )
    select 
        PosIdx, 
        case when isnumeric(Value) = 1 
            then convert(int, Value) 
            else convert(int, null) end as Value 
    from 
        Found

Nó hoạt động bằng cách sử dụng CTE đệ quy làm danh sách các vị trí, từ 1 đến 100 theo mặc định. Nếu bạn cần làm việc với chuỗi dài hơn 100, chỉ cần gọi hàm này bằng cách sử dụng 'tùy chọn (maxrecursion 4000)' như sau:


select * from FnSplitToTableInt
(
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0'
) 
option (maxrecursion 4000)

2
+1 để đề cập đến tùy chọn maxrecursion. Rõ ràng nên sử dụng đệ quy nặng trong môi trường sản xuất, nhưng thật tuyệt khi sử dụng CTE để thực hiện các tác vụ nhập hoặc chuyển đổi dữ liệu nặng.
Tim Medora

3
CREATE FUNCTION Split
(
  @delimited nvarchar(max),
  @delimiter nvarchar(100)
) RETURNS @t TABLE
(
-- Id column can be commented out, not required for sql splitting string
  id int identity(1,1), -- I use this column for numbering splitted parts
  val nvarchar(max)
)
AS
BEGIN
  declare @xml xml
  set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'

  insert into @t(val)
  select
    r.value('.','varchar(max)') as item
  from @xml.nodes('//root/r') as records(r)

  RETURN
END
GO

sử dụng

Select * from dbo.Split(N'1,2,3,4,6',',')

3

CTE đơn giản này sẽ cung cấp những gì cần thiết:

DECLARE @csv varchar(max) = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15';
--append comma to the list for CTE to work correctly
SET @csv = @csv + ',';
--remove double commas (empty entries)
SET @csv = replace(@csv, ',,', ',');
WITH CteCsv AS (
    SELECT CHARINDEX(',', @csv) idx, SUBSTRING(@csv, 1, CHARINDEX(',', @csv) - 1) [Value]
    UNION ALL
    SELECT CHARINDEX(',', @csv, idx + 1), SUBSTRING(@csv, idx + 1, CHARINDEX(',', @csv, idx + 1) - idx - 1) FROM CteCsv
    WHERE CHARINDEX(',', @csv, idx + 1) > 0
)

SELECT [Value] FROM CteCsv

@jinsungy Bạn có thể muốn xem câu trả lời này, nó hiệu quả hơn câu trả lời được chấp nhận và đơn giản hơn.
Michał Turczyn

2

Đây là một phiên bản khác thực sự không có bất kỳ hạn chế nào (ví dụ: ký tự đặc biệt khi sử dụng phương pháp xml, số lượng bản ghi trong phương pháp CTE) và nó chạy nhanh hơn nhiều dựa trên thử nghiệm trên các bản ghi 10M + với độ dài trung bình của chuỗi nguồn là 4000. Hy vọng điều này có thể giúp đỡ

Create function [dbo].[udf_split] (
    @ListString nvarchar(max),
    @Delimiter  nvarchar(1000),
    @IncludeEmpty bit) 
Returns @ListTable TABLE (ID int, ListValue nvarchar(1000))
AS
BEGIN
    Declare @CurrentPosition int, @NextPosition int, @Item nvarchar(max), @ID int, @L int
    Select @ID = 1,
   @L = len(replace(@Delimiter,' ','^')),
            @ListString = @ListString + @Delimiter,
            @CurrentPosition = 1 
    Select @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
   While @NextPosition > 0 Begin
   Set  @Item = LTRIM(RTRIM(SUBSTRING(@ListString, @CurrentPosition, @NextPosition-@CurrentPosition)))
   If      @IncludeEmpty=1 or LEN(@Item)>0 Begin 
     Insert Into @ListTable (ID, ListValue) Values (@ID, @Item)
     Set @ID = @ID+1
   End
   Set  @CurrentPosition = @NextPosition+@L
   Set  @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
  End
    RETURN
END

1

Sử dụng bảng kiểm đếm ở đây là một hàm chuỗi tách (cách tiếp cận tốt nhất có thể) của Jeff Moden

CREATE FUNCTION [dbo].[DelimitedSplit8K]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
     -- enough to cover NVARCHAR(4000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

Được giới thiệu từ Tally OH! Một chức năng Splitter Splitter SQL 8K được cải tiến


0

Blog này đi kèm với một giải pháp khá tốt bằng cách sử dụng XML trong T-SQL.

Đây là chức năng tôi đã đưa ra dựa trên blog đó (thay đổi tên hàm và loại kết quả theo nhu cầu):

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[SplitIntoBigints]
(@List varchar(MAX), @Splitter char)
RETURNS TABLE 
AS
RETURN 
(
    WITH SplittedXML AS(
        SELECT CAST('<v>' + REPLACE(@List, @Splitter, '</v><v>') + '</v>' AS XML) AS Splitted
    )
    SELECT x.v.value('.', 'bigint') AS Value
    FROM SplittedXML
    CROSS APPLY Splitted.nodes('//v') x(v)
)
GO

0
CREATE Function [dbo].[CsvToInt] ( @Array varchar(4000)) 
returns @IntTable table 
(IntValue int)
AS
begin
declare @separator char(1)
set @separator = ','
declare @separator_position int 
declare @array_value varchar(4000) 

set @array = @array + ','

while patindex('%,%' , @array) <> 0 
begin

select @separator_position = patindex('%,%' , @array)
select @array_value = left(@array, @separator_position - 1)

Insert @IntTable
Values (Cast(@array_value as int))
select @array = stuff(@array, 1, @separator_position, '')
end

0
/* *Object:  UserDefinedFunction [dbo].[Split]    Script Date: 10/04/2013 18:18:38* */
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[Split]
(@List varchar(8000),@SplitOn Nvarchar(5))
RETURNS @RtnValue table
(Id int identity(1,1),Value nvarchar(100))
AS
BEGIN
    Set @List = Replace(@List,'''','')
    While (Charindex(@SplitOn,@List)>0)
    Begin

    Insert Into @RtnValue (value)
    Select
    Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))

    Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
    End

    Insert Into @RtnValue (Value)
    Select Value = ltrim(rtrim(@List))

    Return
END
go

Select *
From [Clv].[Split] ('1,2,3,3,3,3,',',')
GO

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.