ฉันจะแยกสตริงเพื่อให้สามารถเข้าถึงรายการ x ได้อย่างไร


493

การใช้ SQL Server ฉันจะแยกสตริงเพื่อให้สามารถเข้าถึงรายการ x ได้อย่างไร

ใช้สตริง "Hello John Smith" ฉันจะแบ่งสตริงตามช่องว่างและเข้าถึงรายการที่ดัชนี 1 ซึ่งควรส่งคืน "John" ได้อย่างไร



5
บิวด์อินของ sql server 2016 msdn.microsoft.com/en-us/library/mt684588.aspx
Tim Abell

4
สูงสุดคำตอบที่นี่เป็น - อย่างน้อยสำหรับฉัน - ค่อนข้างเก่าและค่อนข้างออกลงวันที่ locig แบบโพรเซสซิง, ลูป, การเรียกซ้ำ, CLR, ฟังก์ชั่น, โค้ดหลายบรรทัด ... อาจเป็นเรื่องที่น่าสนใจที่จะอ่านคำตอบ "แอคทีฟ" เพื่อค้นหาแนวทางที่เป็นปัจจุบันมากขึ้น
Shnugo

ฉันได้เพิ่มคำตอบใหม่ด้วยวิธีการที่ทันสมัยมากขึ้น: stackoverflow.com/a/49669994/632604
Gorgi Rankovski

ลองรับองค์ประกอบที่ n ของรายการ -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
José Diz

คำตอบ:


191

คุณอาจพบวิธีแก้ปัญหาในSQL User Defined Function การแยกสตริงที่มีประโยชน์ (จากโครงการรหัส )

คุณสามารถใช้ตรรกะง่ายๆนี้:

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%', @products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products,
                                    0,
                                    PATINDEX('%|%', @products))
        SELECT @individual

        SET @products = SUBSTRING(@products,
                                  LEN(@individual + '|') + 1,
                                  LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END

1
ทำไม SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))ไม่ SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)?
เบ ธ

12
@GateKiller โซลูชันนี้ไม่รองรับ Unicode และใช้รหัสแบบฮาร์ดโค้ด (18,3) ซึ่งไม่ได้ทำให้ฟังก์ชั่น "นำกลับมาใช้" ได้
Filip De Vos

4
ใช้งานได้ แต่จัดสรรหน่วยความจำจำนวนมากและเปลือง CPU
jjxtra

2
ในฐานะของ SQL Server 2016 ขณะนี้มีฟังก์ชั่นในตัวSTRING_SPLITที่จะแยกสตริงและส่งกลับผลลัพธ์ตารางหนึ่งคอลัมน์ซึ่งคุณสามารถใช้ในSELECTคำสั่งหรือที่อื่น ๆ
qJake

น่าเสียดายที่คนที่ฉันทำงานไม่ได้อยู่ในปี 2559 แต่ฉันจะจำไว้ในกรณีที่พวกเขาเคยเป็นผู้นำของรองเท้า ทางออกที่ดีในระหว่างกาล ฉันใช้มันเป็นฟังก์ชั่นและเพิ่มตัวคั่นเป็นอาร์กิวเมนต์
Brandon Griffin

355

ฉันไม่เชื่อว่า SQL Server มีฟังก์ชั่นแยกในตัวดังนั้นนอกเหนือจาก UDF คำตอบอื่น ๆ ที่ฉันรู้คือการจี้ฟังก์ชั่น PARSENAME:

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 

PARSENAME รับสตริงและแยกสตริงตามอักขระช่วงเวลา มันต้องใช้ตัวเลขเป็นอาร์กิวเมนต์ที่สองและหมายเลขนั้นจะระบุเซ็กเมนต์ของสตริงที่จะส่งคืน (ทำงานจากกลับไปข้างหน้า)

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

ปัญหาที่ชัดเจนคือเมื่อสตริงมีจุดอยู่แล้ว ฉันยังคิดว่าการใช้ UDF เป็นวิธีที่ดีที่สุด ... คำแนะนำอื่น ๆ


102
ขอบคุณซาอูล ... ฉันควรชี้ให้เห็นว่าวิธีนี้เป็นทางออกที่ไม่ดีสำหรับการพัฒนาที่แท้จริง PARSENAME คาดว่าจะมีสี่ส่วนเท่านั้นดังนั้นการใช้สตริงที่มีมากกว่าสี่ส่วนจะทำให้ NULL ส่งคืน โซลูชั่น UDF ดีกว่าอย่างเห็นได้ชัด
นาธานฟอร์ด

33
นี่เป็นแฮ็คที่ยอดเยี่ยมและทำให้ฉันร้องไห้ว่าสิ่งนี้เป็นสิ่งที่จำเป็นสำหรับสิ่งที่ friggin ง่าย ๆ ในภาษาจริง
Factor Mystic

36
ในการทำให้ดัชนีทำงานใน "ถูกต้อง" นั่นคือเริ่มต้นที่ 1 ฉันได้จี้จี้ด้วย REVERSE: REVERSE (PARSENAME (แทนที่ (REVERSE ('Hello John Smith'), '', '.') , 1)) - ส่งคืน Hello
NothingsImpossible

3
@FactorMystic รูปแบบปกติครั้งแรกต้องการให้คุณไม่ใส่ค่าหลายค่าในฟิลด์เดียว มันเป็นกฎข้อแรกของ RDBMS SPLIT()ฟังก์ชั่นไม่ได้ให้เพราะมันส่งเสริมการออกแบบฐานข้อมูลที่ไม่ดีและฐานข้อมูลจะไม่ได้รับการเพิ่มประสิทธิภาพในการใช้ข้อมูลที่เก็บไว้ในรูปแบบนี้ RDBMS ไม่มีข้อผูกมัดที่จะช่วยให้นักพัฒนาทำสิ่งที่โง่ที่มันถูกออกแบบมาไม่ให้จัดการ คำตอบที่ถูกต้องจะเสมอเป็น "ปกติฐานข้อมูลของคุณเหมือนที่เราบอกคุณ 40 ปีที่ผ่านมา." ทั้ง SQL และ RDBMS ไม่ควรตำหนิสำหรับการออกแบบที่ไม่ดี
เบคอนบิตส์

8
@BaconBits ในขณะที่ฉันเห็นด้วยในทางทฤษฎีเครื่องมือในการฝึกฝนเช่นนี้มีประโยชน์เมื่อทำให้การออกแบบปกติแย่ลงโดยผู้ที่มาก่อนคุณ
ทิม Abell

110

ขั้นแรกให้สร้างฟังก์ชั่น (โดยใช้ CTE นิพจน์ตารางทั่วไปจะหายไปโดยไม่จำเป็นต้องใช้ตารางชั่วคราว)

 create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

จากนั้นใช้เป็นตารางใด ๆ (หรือแก้ไขเพื่อให้พอดีกับ proc ที่จัดเก็บอยู่ของคุณ) เช่นนี้

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

ปรับปรุง

รุ่นก่อนหน้าจะล้มเหลวในการใส่สายอักขระที่ยาวเกิน 4,000 ตัวอักษร รุ่นนี้ดูแลข้อ จำกัด :

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS s
from tokens
);

GO

การใช้งานยังคงเหมือนเดิม


14
มันหรูหรา แต่ใช้งานได้ 100 องค์ประกอบเท่านั้นเนื่องจากมีข้อ จำกัด ด้านความลึกในการเรียกซ้ำ
Pking

4
@Pking ไม่มีค่าเริ่มต้นคือ100(เพื่อป้องกันการวนซ้ำไม่สิ้นสุด) ใช้MAXRECURSION คำแนะนำในการกำหนดจำนวนของระดับการเรียกซ้ำ ( 0ไป32767, 0คือ "ไม่ จำกัด" - อาจบดขยี้เซิร์ฟเวอร์) BTW คำตอบที่ดีกว่าPARSENAMEเพราะมันเป็นสากล :-) +1
Michał Powaga

เพิ่มmaxrecursionเพื่อให้การแก้ปัญหานี้ในใจคำถามนี้และคำตอบของวิธีการตั้งค่าmaxrecursionตัวเลือกสำหรับการ CTE ภายในตารางมูลค่า-ฟังก์ชัน
Michał Powaga

โดยเฉพาะอ้างอิงคำตอบของ Crisfole - วิธีการของเขาช้าลงบ้าง แต่ก็ง่ายกว่าตัวเลือกอื่น ๆ ส่วนใหญ่
AHiggins

จุดเล็ก ๆ น้อย ๆ แต่การใช้งานไม่เหมือนเดิมเพราะคุณเปลี่ยนชื่อคอลัมน์ดังนั้นจึงsไม่ได้กำหนดไว้อีกต่อไป
Tim Abell

62

โซลูชันส่วนใหญ่ที่นี่ใช้ในขณะที่ลูปหรือ CTE แบบเรียกซ้ำ วิธีการตั้งค่าจะดีกว่าฉันสัญญาถ้าคุณสามารถใช้ตัวคั่นอื่นที่ไม่ใช่ช่องว่าง:

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM 
          ( 
            SELECT n = Number, 
              [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
              CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
            FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
              FROM sys.all_objects) AS x
              WHERE Number <= LEN(@List)
              AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
          ) AS y
        );

ตัวอย่างการใช้งาน:

SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
  WHERE idx = 3;

ผล:

----
blat

คุณสามารถเพิ่มสิ่งที่idxคุณต้องการเป็นอาร์กิวเมนต์ให้กับฟังก์ชันได้ แต่ฉันจะปล่อยให้สิ่งนั้นเป็นแบบฝึกหัดให้กับผู้อ่าน

คุณไม่สามารถทำเช่นนี้กับเพียงพื้นเมืองของSTRING_SPLITฟังก์ชั่นเพิ่มใน SQL Server 2016 เพราะมีการรับประกันว่าการส่งออกจะแสดงผลในคำสั่งของรายการเดิมไม่มี กล่าวอีกนัยหนึ่งถ้าคุณส่งผ่าน3,6,1ผลลัพธ์จะเป็นไปตามลำดับ แต่อาจเป็น1,3,6ไปได้ ฉันขอความช่วยเหลือจากชุมชนในการปรับปรุงฟังก์ชั่นในตัวที่นี่:

ด้วยความคิดเห็นเชิงคุณภาพที่เพียงพอพวกเขาอาจพิจารณาทำการปรับปรุงบางอย่างเหล่านี้:

เพิ่มเติมเกี่ยวกับฟังก์ชั่นแยกทำไม (และพิสูจน์ว่า) ในขณะที่ลูปและ CTE แบบเรียกซ้ำไม่ปรับขนาดและทางเลือกที่ดีกว่าถ้าแยกสตริงที่มาจากชั้นแอปพลิเคชัน:

บน SQL Server 2016 หรือสูงกว่าคุณควรดูSTRING_SPLIT()และSTRING_AGG():


1
คำตอบที่ดีที่สุด IMHO ในบางคำตอบอื่น ๆ ปัญหาของการ จำกัด การเรียกซ้ำ SQL เป็น 100 แต่ไม่ใช่ในกรณีนี้ ใช้งานง่ายและรวดเร็วมาก ปุ่ม +2 อยู่ที่ไหน
T-moty

5
ฉันลองใช้คำต่อคำแบบฟังก์ชั่นนี้กับการใช้งาน: select * from DBO.SplitString('Hello John smith', ' ');และผลลัพธ์ที่ได้คือ: ค่าสวัสดี ello llo o John ohn hn n smith mith ith th h
wwmbes

2
@AaronBertrand ปัญหาดั้งเดิมที่โพสต์โดย GateKiller เกี่ยวข้องกับตัวคั่นช่องว่าง
wwmbes

1
@ user1255933 ที่อยู่
Aaron Bertrand

1
@Michael ใช่มันเป็นเรื่องจริง คุณจะไม่มีตารางให้เลือกหากคุณไม่ได้รับอนุญาตจาก ALTER SCHEMA และไม่สามารถเลือกได้หากคุณไม่มีสิทธิ์ SELECT คุณสามารถขอให้ใครบางคนสร้างฟังก์ชันให้คุณได้ตลอดเวลา . หรือสร้างที่ใดที่หนึ่งที่คุณสามารถสร้างได้ (แม้ชั่วคราวพูดใน tempdb) และในปี 2559+ คุณควรใช้ STRING_SPLIT () และไม่ใช่ฟังก์ชันที่คุณต้องสร้างด้วยตัวเอง
Aaron Bertrand

38

คุณสามารถใช้ประโยชน์จากตาราง Number เพื่อทำการแยกสตริง

สร้างตารางหมายเลขทางกายภาพ:

    create table dbo.Numbers (N int primary key);
    insert into dbo.Numbers
        select top 1000 row_number() over(order by number) from master..spt_values
    go

สร้างตารางทดสอบที่มี 1000000 แถว

    create table #yak (i int identity(1,1) primary key, array varchar(50))

    insert into #yak(array)
        select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
    go

สร้างฟังก์ชั่น

    create function [dbo].[ufn_ParseArray]
        (   @Input      nvarchar(4000), 
            @Delimiter  char(1) = ',',
            @BaseIdent  int
        )
    returns table as
    return  
        (   select  row_number() over (order by n asc) + (@BaseIdent - 1) [i],
                    substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
            from    dbo.Numbers
            where   n <= convert(int, len(@Input)) and
                    substring(@Delimiter + @Input, n, 1) = @Delimiter
        )
    go

การใช้งาน (เอาต์พุต 3 ล้านแถวใน 40 วินาทีบนแล็ปท็อปของฉัน)

    select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)

ทำความสะอาด

    drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]

ประสิทธิภาพที่นี่ไม่น่าทึ่ง แต่การเรียกใช้ฟังก์ชั่นที่มีมากกว่าหนึ่งล้านแถวนั้นไม่ใช่ความคิดที่ดีที่สุด หากทำการแยกสตริงด้วยหลายแถวฉันจะหลีกเลี่ยงการทำงาน


2
IMO ที่ดีที่สุดทางออกอื่น ๆ มีข้อ จำกัด บางอย่าง .. นี่รวดเร็วและสามารถแยกสตริงยาวที่มีองค์ประกอบมากมาย
Pking

ทำไมคุณสั่งซื้อ n มากไปหาน้อย? หากมีรายการสามรายการและเราเริ่มนับที่ 1 รายการแรกจะเป็นหมายเลข 3 และรายการสุดท้ายจะเป็นหมายเลข 1 มันจะไม่ให้ผลลัพธ์ที่เข้าใจง่ายขึ้นหากdescนำออกหรือไม่
ขวาน - ทำกับ SOverflow

1
ตกลงจะใช้งานง่ายขึ้นในทิศทางขึ้น ฉันกำลังติดตามการประชุม parsename () ซึ่งใช้ desc
Nathan Skerl

3
คำอธิบายบางอย่างเกี่ยวกับการทำงานนี้จะดีอย่างไร
Tim Abell

ในการทดสอบกับ 100 ล้านแถวของการแยกวิเคราะห์ถึง 3 ฟิลด์ ufn_ParseArray ไม่เสร็จหลังจาก 25 นาทีในขณะที่REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) จาก @NothingsImpossible เสร็จใน 1.5 นาที @hello_earth โซลูชันของคุณจะเปรียบเทียบกับสตริงที่ยาวกว่าด้วยฟิลด์มากกว่า 4 ฟิลด์อย่างไร
wwmbes

31

คำถามนี้เป็นคำถามไม่ได้เกี่ยวกับวิธีการแยกสตริงแต่เกี่ยวกับวิธีการที่จะได้รับองค์ประกอบที่ n

คำตอบทั้งหมดที่นี่กำลังทำชนิดของการแยกสตริงโดยใช้การเรียกซ้ำ, CTES, หลายCHARINDEX, REVERSEและPATINDEXประดิษฐ์ฟังก์ชั่นการโทรหาวิธีการ CLR ตารางหมายเลขCROSS APPLYs ... คำตอบส่วนใหญ่ครอบคลุมหลายสายรหัส

แต่ - ถ้าคุณไม่ต้องการอะไรมากไปกว่าการได้องค์ประกอบที่ n - สิ่งนี้สามารถทำได้เหมือนจริงแบบหนึ่งซับไม่มี UDF ไม่แม้แต่เลือกย่อย ... และประโยชน์พิเศษ: พิมพ์อย่างปลอดภัย

รับส่วนที่ 2 คั่นด้วยช่องว่าง:

DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')

แน่นอนคุณสามารถใช้ตัวแปรสำหรับตัวคั่นและตำแหน่ง (ใช้sql:columnเพื่อดึงตำแหน่งโดยตรงจากค่าของแบบสอบถาม):

DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')

หากสตริงของคุณอาจมีตัวอักษรต้องห้าม (โดยเฉพาะอย่างยิ่งหนึ่งในนั้น&><) คุณยังสามารถทำเช่นนี้ได้ เพียงใช้FOR XML PATHกับสตริงของคุณก่อนเพื่อแทนที่อักขระที่ต้องห้ามทั้งหมดด้วยลำดับการหลีกเลี่ยงที่เหมาะสมโดยปริยาย

มันเป็นกรณีพิเศษมากถ้า - นอกจากนี้ - คั่นของคุณอัฒภาค ในกรณีนี้ฉันแทนที่ตัวคั่นเป็น '# DLMT #' และแทนที่สิ่งนี้เป็นแท็ก XML ในที่สุด:

SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');

อัปเดตสำหรับ SQL-Server 2016+

STRING_SPLITน่าเศร้านักพัฒนาลืมที่จะกลับมาเป็นส่วนหนึ่งของดัชนีที่มี แต่ใช้ SQL เซิร์ฟเวอร์ 2016+ มีและJSON_VALUEOPENJSON

ด้วยJSON_VALUEเราสามารถผ่านในฐานะอาร์เรย์ของดัชนี

สำหรับเอกสารระบุอย่างชัดเจนOPENJSON

เมื่อ OPENJSON แยกวิเคราะห์อาร์เรย์ JSON ฟังก์ชันจะส่งคืนดัชนีขององค์ประกอบในข้อความ JSON เป็นคีย์

สตริงเช่นความต้องการอะไรมากไปกว่าวงเล็บ:1,2,3 สตริงของคำเช่นความต้องการที่จะเป็น การดำเนินการของสตริงเหล่านี้ง่ายมาก ลองใช้ดูสิ:[1,2,3]
this is an example["this","is","an","example"]

DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;

--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));

- ดูสิ่งนี้สำหรับตำแหน่ง string-splitter ที่ปลอดภัย ( zero-based ):

SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

ในโพสต์นี้ฉันทดสอบวิธีการต่างๆและพบว่าOPENJSONมันเร็วจริงๆ เร็วกว่าวิธี "delimitedSplit8k ()" ที่โด่งดังมาก ...

อัปเดต 2 - รับค่าประเภทที่ปลอดภัย

เราสามารถใช้อาร์เรย์ภายในอาร์เรย์[[]]ง่ายๆโดยการใช้สองเท่า สิ่งนี้อนุญาตให้พิมพ์WITH--clause:

DECLARE  @SomeDelimitedString VARCHAR(100)='part1|1|20190920';

DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');

SELECT @SomeDelimitedString          AS TheOriginal
      ,@JsonArray                    AS TransformedToJSON
      ,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment  VARCHAR(100) '$[0]'
    ,TheSecondFragment INT          '$[1]'
    ,TheThirdFragment  DATE         '$[2]') ValuesFromTheArray

Re: ถ้าสตริงของคุณอาจมีตัวอักษรต้องห้าม ... คุณสามารถใส่ซับสตริงได้เช่น<x><![CDATA[x<&>x]]></x>กัน
ซัลมา

@ SalmanA, ใช่ - CDATAส่วนสามารถจัดการกับเรื่องนี้ได้เช่นกัน ... แต่หลังจากที่พวกเขาไปแล้ว (เปลี่ยนเป็นหนีtext()โดยปริยาย) ฉันไม่ชอบเวทมนตร์ภายใต้ประทุนดังนั้นฉันจึงชอบ(SELECT 'Text with <&>' AS [*] FOR XML PATH(''))วิธีการ สิ่งนี้ดูสะอาดสำหรับฉันและเกิดขึ้นต่อไป ... (เพิ่มเติมเกี่ยวกับ CDATA และ XML )
Shnugo

22

นี่คือ UDF ที่จะทำ มันจะคืนค่าตารางของค่าที่คั่นด้วย, ยังไม่ได้ลองทุกสถานการณ์ แต่ตัวอย่างของคุณใช้ได้ดี


CREATE FUNCTION SplitString 
(
    -- Add the parameters for the function here
    @myString varchar(500),
    @deliminator varchar(10)
)
RETURNS 
@ReturnTable TABLE 
(
    -- Add the column definitions for the TABLE variable here
    [id] [int] IDENTITY(1,1) NOT NULL,
    [part] [varchar](50) NULL
)
AS
BEGIN
        Declare @iSpaces int
        Declare @part varchar(50)

        --initialize spaces
        Select @iSpaces = charindex(@deliminator,@myString,0)
        While @iSpaces > 0

        Begin
            Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

            Insert Into @ReturnTable(part)
            Select @part

    Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))


            Select @iSpaces = charindex(@deliminator,@myString,0)
        end

        If len(@myString) > 0
            Insert Into @ReturnTable
            Select @myString

    RETURN 
END
GO

คุณจะเรียกมันว่า:


Select * From SplitString('Hello John Smith',' ')

แก้ไข: โซลูชันที่อัปเดตเพื่อจัดการตัวคั่นด้วย len> 1 เช่นเดียวกับใน:


select * From SplitString('Hello**John**Smith','**')

ไม่ทำงานสำหรับ select * จาก dbo.ethos_SplitString_fn ('ผู้ชาย, สารประกอบถูกอยู่ที่นี่', ',') ส่วน id ----------- ------------ -------------------------------------- 1 คน 2 ไส้ตะเกียง
ผู้ชาย

2
ระวังด้วย len () เนื่องจากจะไม่ส่งคืนหมายเลขที่ถูกต้องหากอาร์กิวเมนต์มีช่องว่างต่อท้ายเช่น len ('-') = 2
Rory

ไม่ทำงาน: เลือก * จาก dbo.SplitString ('foo, foo test ,,,, foo', ',')
cbp

1
แก้ไขสำหรับ cbp .. เลือก @myString = ซับสตริง (@ mystring, @ iSpaces + len (@deliminator), len (@myString) - charindex (@ deliminator, @ myString, 0))
Alxwest

16

ที่นี่ฉันโพสต์วิธีแก้ปัญหาง่ายๆ

CREATE FUNCTION [dbo].[split](
          @delimited NVARCHAR(MAX),
          @delimiter NVARCHAR(100)
        ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
        AS
        BEGIN
          DECLARE @xml XML
          SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'

          INSERT INTO @t(val)
          SELECT  r.value('.','varchar(MAX)') as item
          FROM  @xml.nodes('/t') as records(r)
          RETURN
        END


ดำเนินการฟังก์ชั่นเช่นนี้

  select * from dbo.split('Hello John Smith',' ')

ฉันชอบวิธีนี้ ขยายเพื่อส่งคืนค่าสเกลาร์ตามคอลัมน์ที่ระบุภายในผลลัพธ์
อลัน

ฉันถูกไฟไหม้ด้วยเครื่องหมาย '&' ในสตริงเพื่อแยกโดยใช้สิ่งนี้
KeithL

10

ในความคิดของฉันพวกคุณกำลังทำให้มันซับซ้อนเกินไป เพียงแค่สร้าง CLR UDF และทำได้

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions {
  [SqlFunction]
  public static SqlString SearchString(string Search) {
    List<string> SearchWords = new List<string>();
    foreach (string s in Search.Split(new char[] { ' ' })) {
      if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
        SearchWords.Add(s);
      }
    }

    return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
  }
};

20
ฉันเดาว่ามันซับซ้อนเกินไปเพราะฉันต้องมี Visual Studio จากนั้นเปิดใช้งาน CLR บนเซิร์ฟเวอร์จากนั้นสร้างและคอมไพล์โครงการและสุดท้ายเพิ่มแอสเซมบลีลงในฐานข้อมูลเพื่อใช้งาน แต่ก็ยังเป็นคำตอบที่น่าสนใจ
Guillermo Gutiérrez

3
@ guillegr123 ไม่จำเป็นต้องซับซ้อน คุณสามารถดาวน์โหลดและติดตั้ง (ฟรี!), SQL #, ซึ่งเป็นไลบรารีของฟังก์ชันและโปรแกรม SQLCLR คุณจะได้รับจากSQLsharp.com ใช่ฉันเป็นผู้เขียน แต่ String_Split รวมอยู่ในรุ่นฟรีแล้ว
โซโลมอน Rutzky

10

สิ่งที่เกี่ยวกับการใช้stringและvalues()คำสั่ง?

DECLARE @str varchar(max)
SET @str = 'Hello John Smith'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

บรรลุผลสำเร็จ

id  item
1   Hello
2   John
3   Smith

1
ฉันใช้คำตอบของคุณ แต่ไม่ได้ผล แต่ฉันแก้ไขและสิ่งนี้ใช้ได้กับการรวมทุกอย่างฉันใช้ sql 2005
นางฟ้า

9

ฉันใช้คำตอบของ frederic แต่สิ่งนี้ไม่ทำงานใน SQL Server 2005

ฉันแก้ไขมันและฉันใช้selectกับunion allและใช้งานได้

DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT  ''' + @str + '''  ' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

และชุดผลลัพธ์คือ:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you

มันยอดเยี่ยมจริง ๆ ที่ฉันเคยเห็นในสิ่งที่ sql มันทำงานให้กับงานของฉันและฉันขอบคุณที่ขอบคุณ!
Abdurrahman I.

ผมตื่นเต้นจริงๆเมื่อฉันเห็นนี้เพราะมันดูสะอาดและง่ายต่อการเข้าใจ แต่น่าเสียดายที่คุณไม่สามารถใส่นี้ภายใน UDF EXECเนื่องจากการที่ EXECเรียกโพรซีเดอร์ที่เก็บไว้แบบ implicitly และคุณไม่สามารถใช้โพรซีเดอร์ที่เก็บใน UDF ได้
Kristen Hammack

ใช้งานได้อย่างสมบูรณ์แบบ !! ฉันกำลังมองหาการใช้ฟังก์ชั่น (SplitStrings_Moden) จากที่นี่: sqlperformance.com/2012/07/t-sql-queries/split-strings#commentsที่ทำสิ่งนี้และใช้เวลาหนึ่งนาทีครึ่งในการแยกข้อมูลและส่งคืน แถวเมื่อใช้หมายเลขบัญชี 4 เท่านั้น ฉันทดสอบเวอร์ชันของคุณด้วยการเข้าร่วมทางซ้ายบนโต๊ะด้วยข้อมูลเกี่ยวกับหมายเลขบัญชีและใช้เวลา 2 หรือ 3 วินาที! ความแตกต่างอย่างมากและทำงานได้อย่างไร้ที่ติ! ฉันจะให้ 20 คะแนนนี้ถ้าเป็นไปได้!
MattE

8

รูปแบบนี้ใช้งานได้ดีและคุณสามารถพูดคุยทั่วไป

Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
                          ^^^^^                                   ^^^^^     ^^^^

ทราบFIELD , INDEXและประเภท

ปล่อยให้ตารางที่มีตัวระบุเช่น

sys.message.1234.warning.A45
sys.message.1235.error.O98
....

จากนั้นคุณสามารถเขียน

SELECT Source         = q.value('(/n[1])', 'varchar(10)'),
       RecordType     = q.value('(/n[2])', 'varchar(20)'),
       RecordNumber   = q.value('(/n[3])', 'int'),
       Status         = q.value('(/n[4])', 'varchar(5)')
FROM   (
         SELECT   q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
         FROM     some_TABLE
       ) Q

แยกและหล่อชิ้นส่วนทั้งหมด


นี่เป็นทางออกเดียวที่ให้คุณส่งไปยังบางประเภทและมีประสิทธิภาพปานกลาง (CLR ยังคงมีประสิทธิภาพมากที่สุด แต่วิธีนี้จัดการ 8gb, 10 โทเค็น, แถวแถว 10M ในเวลาประมาณ 9 นาที (เซิร์ฟเวอร์ aws m3, 4k iops ไดรฟ์ที่จัดสรรแล้ว)
Andrew Hill

7

หากฐานข้อมูลของคุณมีระดับความเข้ากันได้ 130 หรือสูงกว่าคุณสามารถใช้ฟังก์ชันSTRING_SPLITพร้อมกับคำสั่งOFFSET FETCHเพื่อรับรายการเฉพาะตามดัชนี

ในการรับไอเท็มที่ดัชนี N (ขึ้นอยู่กับศูนย์) คุณสามารถใช้รหัสต่อไปนี้

SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY

ในการตรวจสอบระดับความเข้ากันได้ของฐานข้อมูลของคุณให้เรียกใช้รหัสนี้:

SELECT compatibility_level  
FROM sys.databases WHERE name = 'YourDBName';

เคล็ดลับอยู่ใน OFFSET 1 ROWS ซึ่งจะข้ามรายการแรกและจะส่งคืนรายการที่สอง หากดัชนีของคุณเป็นแบบ 0 และ @X เป็นตัวแปรที่เก็บดัชนีรายการที่คุณต้องการดึงข้อมูลคุณสามารถมั่นใจได้ว่า OFFSET
@X

โอเคไม่ได้ใช้สิ่งนี้มาก่อน ... ยินดีที่จะทราบ ... ฉันยังคงต้องการxmlวิธีการตาม -split เนื่องจากช่วยให้สามารถดึงค่าประเภทที่ปลอดภัยและไม่จำเป็นต้องมีแบบสอบถามย่อย แต่นี่เป็น สิ่งที่ดี. +1 จากด้านของฉัน
Shnugo

3
ปัญหาที่นี่คือ STRING_SPLIT ไม่รับประกันการสั่งซื้อผลลัพธ์ที่ส่งคืน ดังนั้นไอเท็มของคุณ 1 อาจจะใช่หรือไม่ใช่ไอเท็ม 1 ของฉันก็ได้
user1443098

@GorgiRankovski, ใช้STRING_SPLITความต้องการสำหรับ v2016 + ในกรณีนี้มันเป็นเรื่องดีที่จะใช้หรือOPENJSON JSON_VALUEคุณอาจต้องการตรวจสอบคำตอบของฉัน
Shnugo

6

ฉันกำลังมองหาทางออกบนเน็ตและด้านล่างใช้ได้สำหรับฉัน อ้าง

และคุณเรียกใช้ฟังก์ชันดังนี้:

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

คุณไม่สามารถเข้าถึงรายการ Nth ได้อย่างง่ายดายโดยใช้ฟังก์ชั่นนี้
Björn Lindqvist

6

อีกส่วนหนึ่งได้รับส่วนหนึ่งของสตริงโดยฟังก์ชั่น delimeter:

create function GetStringPartByDelimeter (
    @value as nvarchar(max),
    @delimeter as nvarchar(max),
    @position as int
) returns NVARCHAR(MAX) 
AS BEGIN
    declare @startPos as int
    declare @endPos as int
    set @endPos = -1
    while (@position > 0 and @endPos != 0) begin
        set @startPos = @endPos + 1
        set @endPos = charindex(@delimeter, @value, @startPos)

        if(@position = 1) begin
            if(@endPos = 0)
                set @endPos = len(@value) + 1

            return substring(@value, @startPos, @endPos - @startPos)
        end

        set @position = @position - 1
    end

    return null
end

และการใช้งาน:

select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)

ซึ่งผลตอบแทน:

c

ฉันชอบวิธีนี้เป็นตัวเลือกในการส่งคืนสตริงย่อยเดียวซึ่งต่างกับการแยกวิเคราะห์ตารางที่คุณต้องเลือกจาก การใช้ตารางผลลัพธ์มีประโยชน์ แต่สำหรับสิ่งที่ฉันต้องการสิ่งนี้ได้ผลอย่างสมบูรณ์
James H

5

ลองสิ่งนี้:

CREATE function [SplitWordList]
(
 @list varchar(8000)
)
returns @t table 
(
 Word varchar(50) not null,
 Position int identity(1,1) not null
)
as begin
  declare 
    @pos int,
    @lpos int,
    @item varchar(100),
    @ignore varchar(100),
    @dl int,
    @a1 int,
    @a2 int,
    @z1 int,
    @z2 int,
    @n1 int,
    @n2 int,
    @c varchar(1),
    @a smallint
  select 
    @a1 = ascii('a'),
    @a2 = ascii('A'),
    @z1 = ascii('z'),
    @z2 = ascii('Z'),
    @n1 = ascii('0'),
    @n2 = ascii('9')
  set @ignore = '''"'
  set @pos = 1
  set @dl = datalength(@list)
  set @lpos = 1
  set @item = ''
  while (@pos <= @dl) begin
    set @c = substring(@list, @pos, 1)
    if (@ignore not like '%' + @c + '%') begin
      set @a = ascii(@c)
      if ((@a >= @a1) and (@a <= @z1))  
        or ((@a >= @a2) and (@a <= @z2))
        or ((@a >= @n1) and (@a <= @n2))
      begin
        set @item = @item + @c
      end else if (@item > '') begin
        insert into @t values (@item)
        set @item = ''
      end
    end 
    set @pos = @pos + 1
  end
  if (@item > '') begin
    insert into @t values (@item)
  end
  return
end

ทดสอบแบบนี้:

select * from SplitWordList('Hello John Smith')

ฉันผ่านมันไปแล้ว & มันก็เหมือนกับสิ่งที่ฉันต้องการ! แม้ว่าฉันจะสามารถปรับแต่งมันเพื่อละเว้นอักขระพิเศษที่ฉันเลือก!
Vikas

5

ตัวอย่างต่อไปนี้ใช้ CTE แบบเรียกซ้ำ

อัปเดต 18.09.2013

CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
 (
  SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter,  @List + @Delimiter)) AS val,
         CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval, 
         1 AS [level]
  UNION ALL
  SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
         CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
         [level] + 1
  FROM cte
  WHERE stval != ''
  )
  INSERT @returns
  SELECT REPLACE(val, ' ','' ) AS val, [level]
  FROM cte
  WHERE val > ''
  RETURN
END

การสาธิตเกี่ยวกับSQLFiddle


2


    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

2

คุณสามารถแยกสตริงใน SQL โดยไม่จำเป็นต้องมีฟังก์ชั่น:

DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'varchar(36)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);

หากคุณต้องการสนับสนุนสตริงที่กำหนดเอง (ที่มีอักขระพิเศษ xml)

DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "Conex"<html>,Barnes & Noble,abc,def,ghi'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'nvarchar(MAX)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol); 

1

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันคิดว่าบางคนสามารถได้ประโยชน์จากโซลูชันของฉัน

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

SQL FIDDLE

ข้อดี:

  • มันแยก deliminator ย่อย 3 สตริงทั้งหมดโดย ''
  • หนึ่งจะต้องไม่ใช้ในขณะที่วงเพราะมันจะลดประสิทธิภาพ
  • ไม่จำเป็นต้อง Pivot เนื่องจากสตริงย่อยผลลัพธ์จะแสดงในหนึ่งแถว

ข้อ จำกัด :

  • เราจะต้องรู้จำนวนรวมทั้งหมด ของช่องว่าง (สตริงย่อย)

หมายเหตุ : วิธีแก้ปัญหาสามารถมอบสตริงย่อยได้สูงสุด N

เพื่อเอาชนะข้อ จำกัด ที่เราสามารถใช้ต่อไปโทษ

แต่วิธีการแก้ปัญหาข้างต้นไม่สามารถใช้ในตารางได้อีก (Actaully i ไม่สามารถใช้งานได้)

ฉันหวังว่าโซลูชันนี้จะช่วยได้บ้าง

อัปเดต:ในกรณีที่มีการบันทึก> 50000 ไม่แนะนำให้ใช้LOOPSเนื่องจากจะทำให้ประสิทธิภาพลดลง


1

วิธีการแก้ปัญหาการติดตั้งตามเพียวใช้TVFกับ CTErecursive คุณสามารถJOINและAPPLYฟังก์ชั่นนี้ไปยังชุดข้อมูลใด ๆ

create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
    select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
    union all
    select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
    , left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
    , [no] + 1 [no]
    from r where value > '')

select ltrim(x) [value], [no] [index] from r where x is not null;
go

การใช้งาน:

select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;

ผลลัพธ์:

value   index
-------------
John    1

1

คำตอบอื่น ๆ เกือบทั้งหมดกำลังแทนที่สายอักขระที่ถูกแยกซึ่งทำให้สิ้นเปลืองรอบ CPU และทำการจัดสรรหน่วยความจำที่ไม่จำเป็น

ฉันครอบคลุมวิธีที่ดีกว่าในการแยกสตริงที่นี่: http://www.digitalruby.com/split-string-sql-server/

นี่คือรหัส:

SET NOCOUNT ON

-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1

SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)

WHILE @SplitEndPos > 0
BEGIN
    SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
    INSERT @SplitStringTable (Value) VALUES (@SplitValue)
    SET @SplitStartPos = @SplitEndPos + 1
    SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END

SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)

SET NOCOUNT OFF

-- You can select or join with the values in @SplitStringTable at this point.

0

โซลูชัน CTE แบบเรียกซ้ำพร้อมกับความเจ็บปวดของเซิร์ฟเวอร์ทดสอบ

การติดตั้ง Schema MS SQL Server 2008 :

create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');

แบบสอบถาม 1 :

with cte as
   ( select 
        left( Courses, charindex( ' ' , Courses) ) as a_l,
        cast( substring( Courses, 
                         charindex( ' ' , Courses) + 1 , 
                         len(Courses ) ) + ' ' 
              as varchar(100) )  as a_r,
        Courses as a,
        0 as n
     from Course t
    union all
      select 
        left(a_r, charindex( ' ' , a_r) ) as a_l,
        substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
        cte.a,
        cte.n + 1 as n
    from Course t inner join cte 
         on t.Courses = cte.a and len( a_r ) > 0

   )
select a_l, n from cte
--where N = 1

ผลลัพธ์ :

|    A_L | N |
|--------|---|
| Hello  | 0 |
|  John  | 1 |
| Smith  | 2 |

0

ในขณะที่คล้ายกับคำตอบจาก xml โดย josejuan ฉันพบว่าการประมวลผลเส้นทาง xml เพียงครั้งเดียวจากนั้นการหมุนรอบมีประสิทธิภาพปานกลาง:

select ID,
    [3] as PathProvidingID,
    [4] as PathProvider,
    [5] as ComponentProvidingID,
    [6] as ComponentProviding,
    [7] as InputRecievingID,
    [8] as InputRecieving,
    [9] as RowsPassed,
    [10] as InputRecieving2
    from
    (
    select id,message,d.* from sysssislog cross apply       ( 
          SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
              row_number() over(order by y.i) as rn
          FROM 
          ( 
             SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       ) d
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as tokens 
    pivot 
    ( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10]) 
    ) as data

วิ่งใน 8:30

select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
 from
(
    select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
         from sysssislog 
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as data

วิ่งใน 9:20


0
CREATE FUNCTION [dbo].[fnSplitString] 
( 
    @string NVARCHAR(MAX), 
    @delimiter CHAR(1) 
) 
RETURNS @output TABLE(splitdata NVARCHAR(MAX) 
) 
BEGIN 
    DECLARE @start INT, @end INT 
    SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
    WHILE @start < LEN(@string) + 1 BEGIN 
        IF @end = 0  
            SET @end = LEN(@string) + 1

        INSERT INTO @output (splitdata)  
        VALUES(SUBSTRING(@string, @start, @end - @start)) 
        SET @start = @end + 1 
        SET @end = CHARINDEX(@delimiter, @string, @start)

    END 
    RETURN 
END

และใช้มัน

select *from dbo.fnSplitString('Querying SQL Server','')

0

หากใครต้องการได้รับข้อความที่แยกจากกันเพียงส่วนหนึ่งสามารถใช้สิ่งนี้

เลือก * จาก 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
  )

0

ฉัน devoloped นี้

declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';

while CHARINDEX(@splitter,@x) != 0
begin
    set @item = LEFT(@x,CHARINDEX(@splitter,@x))
    set @x    = RIGHT(@x,len(@x)-len(@item) )
     select @item as item, @x as x;
end

สิ่งเดียวที่คุณควรใส่ใจคือจุด '.' จุดสิ้นสุดของ @x นั้นควรอยู่ที่นั่นเสมอ


0

การสร้างโซลูชัน @NothingsImossible หรือแทนที่จะแสดงความคิดเห็นต่อคำตอบที่ได้รับการโหวตมากที่สุด (ด้านล่างของคำตอบที่ยอมรับ) ฉันพบว่ารวดเร็วและสกปรกดังต่อไปนี้โซลูชันที่ตอบสนองความต้องการของฉันเอง

รับสาย "ก่อน; ที่สอง; ที่สาม; ที่สี่; ที่ห้า" พูดว่าฉันต้องการที่จะได้รับโทเค็นที่สาม มันจะทำงานได้ก็ต่อเมื่อเรารู้ว่ามีกี่โทเค็นสตริงที่จะมี - ในกรณีนี้คือ 5. ดังนั้นวิธีการของฉันคือการตัดโทเค็นสองอันสุดท้ายออกไป (เคียวรีด้านใน) แล้วสับโทเค็นสองอันแรก แบบสอบถามด้านนอก)

ฉันรู้ว่านี่น่าเกลียดและครอบคลุมเงื่อนไขเฉพาะที่ฉันอยู่ แต่กำลังโพสต์ไว้ในกรณีที่มีคนพบว่ามีประโยชน์ ไชโย

select 
    REVERSE(
        SUBSTRING(
            reverse_substring, 
            0, 
            CHARINDEX(';', reverse_substring)
        )
    ) 
from 
(
    select 
        msg,
        SUBSTRING(
            REVERSE(msg), 
            CHARINDEX(
                ';', 
                REVERSE(msg), 
                CHARINDEX(
                    ';',
                    REVERSE(msg)
                )+1
            )+1,
            1000
        ) reverse_substring
    from 
    (
        select 'first;second;third;fourth;fifth' msg
    ) a
) b

ใช้งานได้เฉพาะในกรณีที่เรารู้ว่ามีกี่โทเค็นสตริงที่จะมี - ข้อ จำกัด ที่แตกหัก ...
Shnugo

0
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))

0

เริ่มต้นด้วยSQL Server 2016เราstring_split

DECLARE @string varchar(100) = 'Richard, Mike, Mark'

SELECT value FROM string_split(@string, ',')

นี่เป็นสิ่งที่ดีและดี แต่ไม่ได้ตอบคำถามว่าได้ผลลัพธ์ที่ n
Johnie Karr

STRING_SPLITไม่รับประกันว่าจะส่งคืนคำสั่งซื้อเดียวกัน แต่OPENJSONทำ (ดูคำตอบของฉัน(ส่วนการอัปเดต) )
Shnugo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.