แยกฟังก์ชั่นเทียบเท่าใน T-SQL?


128

ฉันต้องการแยก '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ... ' (คั่นด้วยเครื่องหมายจุลภาค) เป็นตัวแปรตารางหรือตาราง .

ใครบ้างมีฟังก์ชั่นที่ส่งกลับแต่ละคนในแถว?


http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=50648การเลือกวิธีการที่แตกต่างกัน
adolf กระเทียม

1
Erland Sommarskog รักษาคำตอบที่เชื่อถือได้สำหรับคำถามนี้ในช่วง 12 ปีที่ผ่านมา: http://www.sommarskog.se/arrays-in-sql.htmlมันไม่คุ้มที่จะทำซ้ำตัวเลือกทั้งหมดที่นี่ใน StackOverflow เพียงไปที่หน้าของเขาและ คุณจะได้เรียนรู้ทุกสิ่งที่คุณอยากรู้
Portman

2
ฉันเพิ่งทำการศึกษาเล็กน้อยเปรียบเทียบวิธีที่พบมากที่สุดกับปัญหานี้ที่อาจมีมูลค่าอ่าน: sqlperformance.com/2012/07/t-sql-queries/split-stringsและsqlperformance.com/2012/08/t- sql-query / …
Aaron Bertrand

1
อาจเป็นไปได้ซ้ำกันของสตริงการแยกใน SQL
Luv

ดูเหมือนว่าคุณจะได้คำตอบที่ดีที่นี่ ทำไมไม่ทำเครื่องหมายหนึ่งในนั้นเป็นคำตอบหรืออธิบายปัญหาของคุณโดยละเอียดยิ่งขึ้นหากยังไม่ได้รับคำตอบ
RyanfaeScotland

คำตอบ:


51

นี่คือวิธีการแก้ปัญหาค่อนข้างล้าสมัย:

/*
    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

ใน SQL Server 2008 คุณสามารถทำได้เหมือนกันด้วยรหัส. NET อาจจะทำงานได้เร็วขึ้น แต่วิธีการนี้ง่ายต่อการจัดการ


ขอบคุณฉันอยากรู้ มีข้อผิดพลาดที่นี่หรือไม่? ฉันเขียนรหัสนี้เมื่อ 6 ปีที่แล้วและมันก็ใช้ได้ดีมาตั้งแต่เมื่อไหร่
XOR

ฉันเห็นด้วย. นี่เป็นทางออกที่ดีมากเมื่อคุณไม่ต้องการมีส่วนร่วมในการสร้างพารามิเตอร์ประเภทตารางซึ่งอาจเป็นกรณีในอินสแตนซ์ของฉัน DBA ได้ล็อคคุณสมบัติดังกล่าวแล้วและจะไม่อนุญาตให้ทำได้ ขอบคุณ XOR!
dscarr

ประกาศ VarString NVARCHAR (2048) = 'ไมค์ / จอห์น / มิโกะ / แมตต์'; ประกาศ CaracString NVARCHAR (1) = '/'; เลือก * จาก dbo.FnSplitString (VarString, CaracString)
fernando yevenes

55

ลองสิ่งนี้

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)

หรือ

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

วิธีอื่น ๆ อีกมากมายที่ทำแบบเดียวกันนี้อยู่ที่นี่จะแยกสตริงที่คั่นด้วยจุลภาคได้อย่างไร


9
หมายเหตุสำหรับทุกคนค้นหาตัวแยกสตริงโดยทั่วไป: การแก้ปัญหาครั้งแรกให้ที่นี่ไม่ได้เป็นแยกสตริงทั่วไป - มันมีความปลอดภัยเท่านั้นถ้าคุณแน่ใจว่าการป้อนข้อมูลจะไม่เคยมี<, >หรือ&(เช่นการป้อนข้อมูลเป็นลำดับของจำนวนเต็ม) อักขระสามตัวใด ๆ ข้างต้นจะทำให้คุณได้รับข้อผิดพลาดในการแยกวิเคราะห์แทนผลลัพธ์ที่คาดหวัง
miroxlav

1
เหตุการณ์ที่มีปัญหาที่กล่าวถึงโดย miroxlav (ซึ่งควรแก้ไขได้ด้วยความคิด) นี่เป็นหนึ่งในโซลูชั่นที่สร้างสรรค์ที่สุดที่ฉันค้นพบ (ตอนแรก)! ดีมาก!
เมเจอร์แมนน์

บรรทัดที่จริงควรจะเป็นSELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter) B + 1ทำให้แตกต่างใหญ่ ทดสอบที่นี่ด้วยช่องว่างเป็นตัวคั่นไม่สามารถใช้งานได้หากไม่มีการแก้ไขนี้ SELECT b, CHARINDEX(@delimiter, @str, b+1) + LEN(@delimiter)
JwJosefy

@miroxlav นอกจากนี้จากประสบการณ์ของฉันการใช้ XML เพื่อแยกสตริงเป็นทางอ้อมที่มีราคาแพงมาก
underscore_d

สุดยอดโซลูชั่น! น่าสังเกตว่าผู้ใช้อาจต้องการเพิ่มMAXRECURSIONตัวเลือกในการแยกชิ้นส่วนมากกว่า 100 ชิ้นแทนที่LENด้วยบางสิ่งจากstackoverflow.com/q/2025585เพื่อจัดการช่องว่างและไม่รวมNULLแถวสำหรับNULLอินพุต
Kevinoid

27

คุณติดแท็ก SQL Server 2008 นี้ แต่ผู้เยี่ยมชมในอนาคตสำหรับคำถามนี้ (โดยใช้ SQL Server 2016+) มีแนวโน้มว่าคุณอยากรู้ STRING_SPLITมีแนวโน้มที่จะต้องการทราบเกี่ยวกับ

ด้วยฟังก์ชั่นบิวอินใหม่นี้คุณสามารถใช้งานได้ทันที

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

ข้อ จำกัด ของการทำงานนี้และบางส่วนแนวโน้มผลของการทดสอบประสิทธิภาพการทำงานบางอย่างอยู่ในบล็อกโพสต์นี้โดยแอรอนเบอร์ทรานด์


13

นี่เป็นเหมือน NET สำหรับคนที่คุ้นเคยกับฟังก์ชั่นดังกล่าว:

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

นี่คือฟังก์ชั่นแยกที่คุณถาม

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('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)

แหล่งที่มาของการตอบสนองนี้: http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited


ในขณะที่สิ่งนี้อาจตอบคำถามในทางทฤษฎีมันก็ควรที่จะรวมส่วนสำคัญของคำตอบที่นี่และให้ลิงค์สำหรับการอ้างอิง
Xavi López

1
@Xavi: ตกลงฉันได้รวมส่วนสำคัญของคำตอบแล้ว ขอบคุณสำหรับคำใบ้ของคุณ
หมดเวลา Bejenariu

3

ฉันถูกล่อลวงให้บีบในวิธีที่ฉันโปรดปราน ตารางผลลัพธ์จะประกอบด้วย 2 คอลัมน์: PosIdx สำหรับตำแหน่งของจำนวนเต็มที่พบ และค่าเป็นจำนวนเต็ม


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

มันทำงานโดยใช้ recursive CTE เป็นรายการของตำแหน่งจาก 1 ถึง 100 โดยค่าเริ่มต้น หากคุณต้องการทำงานกับสตริงที่ยาวกว่า 100 เพียงเรียกใช้ฟังก์ชันนี้โดยใช้ 'ตัวเลือก (maxrecursion 4000)' ดังนี้:


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 สำหรับการกล่าวถึงตัวเลือกสูงสุด เห็นได้ชัดว่าการเรียกซ้ำอย่างหนักควรใช้ด้วยความระมัดระวังในสภาพแวดล้อมการผลิต แต่มันยอดเยี่ยมสำหรับการใช้ CTE เพื่อดำเนินการนำเข้าหรือแปลงข้อมูลจำนวนมาก
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

การใช้

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

3

CTE แบบง่ายนี้จะให้สิ่งที่ต้องการ:

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 คุณอาจต้องการดูคำตอบนี้มันมีประสิทธิภาพมากกว่าคำตอบที่ยอมรับและเรียบง่ายกว่า
Michał Turczyn

2

นี่เป็นอีกเวอร์ชั่นที่ไม่มีข้อ จำกัด ใด ๆ (เช่น: chars พิเศษเมื่อใช้วิธี xml จำนวนเร็กคอร์ดในวิธีการ CTE) และมันทำงานได้เร็วขึ้นมากขึ้นอยู่กับการทดสอบในเร็กคอร์ด 10M + ที่มีความยาวเฉลี่ยของสตริงที่ 4000 สามารถช่วยได้

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

การใช้ตารางนับที่นี่เป็นฟังก์ชั่นหนึ่งของสตริงการแยก (วิธีที่ดีที่สุด) โดย 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
;

อ้างอิงจากTally OH! ฟังก์ชั่น“ CSV Splitter” SQL 8K ที่ได้รับการปรับปรุง


0

บล็อกนี้มาพร้อมกับโซลูชันที่ดีโดยใช้ XML ใน T-SQL

นี่คือฟังก์ชั่นที่ฉันใช้กับบล็อกนั้น (เปลี่ยนชื่อฟังก์ชั่นและประเภทของนักแสดงต่อความต้องการ):

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

-4

คุณเขียนฟังก์ชันนี้ในเซิร์ฟเวอร์ sql หลังจากปัญหานั้นจะได้รับการแก้ไข

http://csharpdotnetsol.blogspot.in/2013/12/csv-function-in-sql-server-for-divide.html


อย่าคัดลอกลิงค์เท่านั้น ... นั่นไม่ใช่สัญญาณของคำถามที่ดี ... คุณต้องอธิบายรายละเอียดของแอนเนอร์
The Hungry Dictator
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.