Як розділити рядок, щоб отримати доступ до елемента x?


493

Як використовувати SQL Server, як розділити рядок, щоб отримати доступ до елемента x?

Візьміть рядок "Привіт Джон Сміт". Як я можу розділити рядок на пробіл та отримати доступ до елемента в індексі 1, який повинен повернути "Джон"?



5
вбудований на сервері sql 2016 msdn.microsoft.com/en-us/library/mt684588.aspx
Тім Абел

4
У високі відповіді тут - принаймні , для мене - досить старомодний і досить старомодною. Процедурний локус, цикли, рекурсії, CLR, функції, багато рядків коду ... Можливо, буде цікаво прочитати "активні" відповіді, щоб знайти більш сучасні підходи.
Шнуго

Я додав новий відповідь більш уточнений підхід: stackoverflow.com/a/49669994/632604
Gorgi Rankovski

Спробуйте дістати n-й елемент списку -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
Жозе Діз

Відповіді:


191

Ви можете знайти рішення у визначеній користувачем функції SQL для аналізу розмежованого рядка (від проекту Code ).

Ви можете використовувати цю просту логіку:

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), що не робить його життєздатною функцією "багаторазового використання".
Філіп Де Вос

4
Це працює, але виділяє багато пам’яті та витрачає процесор.
jjxtra

2
Станом на SQL Server 2016, тепер є вбудована функція, STRING_SPLITяка розділить рядок і поверне результат таблиці з одним стовпцем, який ви можете використовувати в SELECTоператорі чи в іншому місці.
qJake

Шкода, що хлопці, над якими я працюю, не є у 2016 році. Але я пам’ятаю про це, якщо вони коли-небудь вийдуть на лідери взуття. Чудове рішення в проміжку. Я реалізував це як функцію і додав роздільник як аргумент.
Брендон Гріффін

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. Рішення АДС явно кращі.
Натан Бедфорд

33
Це великий злом, а також змушує мене плакати, що щось подібне потрібно для чогось такого фриґгіна простого на справжніх мовах.
Фактор Містик

36
Щоб індекси працювали «правильним» способом, тобто, починаючи з 1, я викрав ваш викрадник за допомогою REVERSE: REVERSE (PARSENAME (REPLACE (REVERSE ('Hello John Smith') ''), '', '.') , 1)) - Повертається Привіт
NothingsImpossible

3
Перша нормальна форма @FactorMystic вимагає, щоб ви не вводили кілька значень в одне поле. Це буквально перше правило RDBMS. SPLIT()Функція не входить в комплект , тому що вона заохочує поганий дизайн бази даних, і база даних не буде оптимізована для використання даних , збережених в цьому форматі. РСУБД не зобов'язаний допомагають розробникам робити дурниці , що він був розроблений НЕ для ручки. Правильна відповідь завжди буде "Нормалізуйте свою базу даних, як ми говорили вам 40 років тому". Ні SQL, ні RDBMS не винні в поганому дизайні.
Шматочки бекону

8
@BaconBits, хоча я погоджуюсь теоретично, на практиці такі інструменти корисні при нормалізації поганого дизайну, виробленого кимось, хто прийшов до вас.
Тім Абелл

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

Потім використовуйте його як будь-яку таблицю (або змініть її, щоб вона вмістилась у вашому наявному сховищі), як це.

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

Оновлення

Попередня версія не змогла ввести рядок довше 4000 знаків. Ця версія піклується про обмеження:

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це "немає межі" - може зруйнувати сервер). До речі, набагато краща відповідь, ніж це PARSENAME, тому що це універсально :-). +1
Michał Powaga

Додаючи maxrecursionдо цього рішення, пам’ятайте про це питання та його відповіді. Як налаштувати maxrecursionопцію для CTE всередині функції «Таблиця з оцінкою» .
Michał Powaga

Зокрема, посилайтесь на відповідь Крісфола - його метод дещо уповільнює, але простіший за більшість інших варіантів.
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
Найкраща відповідь, ІМХО. В деяких інших відповідях виникає проблема обмеження рекурсії SQL 100, але не в цьому випадку. Дуже швидка і дуже проста реалізація. Де кнопка +2?
T-moty

5
Я спробував цю функцію дослівно з використанням: select * from DBO.SplitString('Hello John smith', ' ');і отриманий результат був: Значення Привіт, елло, ло о, Джон он, хн, Сміт, Міт, І-та,
Ч

2
@AaronBertrand Оригінальна проблема, опублікована GateKiller, включає роздільник місця.
wwmbes

1
@ user1255933 Адресовано
Аарон Бертран

1
@Michael Так, це правда. У вас також не було б таблиці, з якої можна було б вибрати, якби у вас не було дозволу ALTER SCHEMA, і ви не зможете вибрати з нього, якщо у вас немає дозволу SELECT. Ви завжди можете попросити когось створити функцію для вас . Або створити його десь ви можете створити (навіть тимчасово, скажімо, у tempdb). І в 2016+ ви повинні використовувати STRING_SPLIT (), а не функцію, яку ви все одно повинні створювати.
Аарон Бертран

38

Ви можете використовувати таблицю номерів, щоб зробити розбір рядків.

Створіть таблицю фізичних чисел:

    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
Домовились, було б більш інтуїтивно зрозумілим у напрямку Asc. Я дотримувався конвенції parsename (), де використовується desc
Nathan Skerl

3
дещо пояснення, як це буде чудово
Тім Абел,

Під час тесту на 100 мільйонів рядків з трьома полями для розбору, ufn_ParseArray не закінчився через 25 хвилин, тоді як REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) з @NothingsImpossible завершено через 1,5 хвилини. @hello_earth Як би ваше рішення порівнювало довші рядки з більш ніж 4 полями?
wwmbes

31

Це питання не про струнний розкол , а про те, як отримати n-й елемент .

Всі відповіді тут роблять яке - то рядок розщеплення з допомогою рекурсії, CTEз, множинні CHARINDEX, REVERSEі PATINDEX, придумуючи функції, виклик методів CLR, кількість таблиць, CROSS APPLYS ... Більшість відповідей охоплюють безліч рядків коди.

Але - якщо ви дійсно не хочете нічого, крім підходу до отримання n-го елемента - це можна зробити як справжній однолінійний , без UDF, навіть не підбір ... І як додаткова перевага: введіть safe

Розмістіть частину 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-сервера 2016+

На жаль, розробники забули повернути індекс частини за допомогою STRING_SPLIT. Але, використовуючи SQL-Server 2016+, є JSON_VALUEі OPENJSON.

З 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,']'));

--Перегляньте це для безпечного розбиття рядків ( нульовий ):

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

У цій публікації я перевірив різні підходи і виявив, що OPENJSONце дійсно швидко. Навіть набагато швидше, ніж відомий метод "delimitedSplit8k ()" ...

ОНОВЛЕННЯ 2 - Отримайте значення безпечного типу

Ми можемо використовувати масив у масиві просто, використовуючи подвійний [[]]. Це дозволяє вводити WITH-клазу:

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 ).
Шнуго

22

Ось АДС, яка це зробить. Він поверне таблицю з обмеженими значеннями, не випробував усі сценарії, але ваш приклад працює чудово.


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','**')

Не вдалося вибрати * від dbo.ethos_SplitString_fn ('хлопець, гніт, був тут', ',') ідентифікаційна частина ----------- ------------ -------------------------------------- 1 хлопець 2 гніт
Хлопець

2
стежте за len (), оскільки він не поверне правильне число, якщо його аргумент має пробіли. Наприклад, len ('-') = 2.
Rory

Не працює на: виберіть * від dbo.SplitString ('foo, foo test ,,,, foo', ',')
cbp

1
Виправити для cbp .. Виберіть @myString = substring (@ mystring, @ iSpaces + len (@deliminator), len (@myString) - charindex (@ роздільник, @ 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',' ')

Мені сподобалось це рішення. Розгорнуто його, щоб повернути скалярне значення на основі вказаного стовпця в результатах.
Алан

Мене спалили
символом

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 на сервері, потім створити та компілювати проект і, нарешті, додати збірки до бази даних, щоб використовувати його. Але все ж цікава відповідь.
Гільєрмо Гутьеррес

3
@ guillegr123, це не повинно бути складним. Ви можете просто завантажити та встановити (безкоштовно!) SQL #, що є бібліотекою функцій та програм SQLCLR. Ви можете отримати його з SQLsharp.com . Так, я автор, але String_Split включений у безкоштовну версію.
Соломон Руцький

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 речі, він працював на мою роботу, і я ціную це, дякую!
Абдуррахман І.

Я дуже хвилювався, коли побачив це, тому що це виглядало так чисто і легко зрозуміти, але, на жаль, ви не можете помістити це всередині АДС через EXEC. EXECнеявно викликає збережену процедуру, і ви не можете використовувати збережені процедури в UDF.
Крістен Хаммак

Це прекрасно працює !! я розглядав функцію (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 та TYPE .

Нехай якась таблиця з ідентифікаторами, як

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 все ще є найбільш ефективним, але такий підхід обробляє таблицю рядків 8 ГБ, 10 токенів, 10 М приблизно за 9 хв (сервер м3, 4 кп передбачений привід)
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 ROWS
Gorgi Rankovski

Гаразд, не користувався цим раніше ... Приємно знати ... Я все-таки віддаю перевагу xmlпідходу, заснованому на спліт, оскільки він дозволяє отримати безпечний тип значень і не потребує підзапиту, але це хороший. +1 з мого боку
Шнуго

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

Ви не можете легко отримати доступ до N-го елемента за допомогою цієї функції.
Бьорн Ліндквіст

6

Ще одна отримана п’ята частина рядка за допомогою функції деліметра:

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

Мені подобається це рішення як варіант повернення однієї підрядки на відміну від отримання розібраної таблиці, з якої потім потрібно вибрати. Використання результату таблиці має своє використання, але для того, що мені було потрібно, це працювало чудово.
Джеймс Н

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

Я пережив це, і це абсолютно як те, що я хочу! навіть я можу налаштувати його для ігнорування спеціальних символів, які я вибираю!
Вікас

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

Переваги:

  • Він розділяє всі три роздільники підрядків на ''.
  • Не потрібно використовувати цикл while, оскільки це знижує продуктивність.
  • Не потрібно перемикати, оскільки весь результат підрядка буде відображатися в одному рядку

Обмеження:

  • Треба знати загальну ні. пробілів (підряд).

Примітка : рішення може надати підрядку до N.

Щоб подолати обмеження, ми можемо скористатися наступним посиланням .

Але знову ж таки вищезазначене рішення не можна використовувати в таблиці (Actaully я не зміг ним скористатися).

Ще раз сподіваюся, що це рішення може допомогти комусь.

Оновлення: У разі записів> 50000 НЕ рекомендується використовувати , LOOPSоскільки це призведе до погіршення продуктивності


1

Чисте рішення на основі використання TVFз використанням рекурсивного CTE. Ви можете 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

Майже всі інші відповіді замінюють розділений рядок, який витрачає цикли процесора та виконує непотрібні розподіли пам'яті.

Тут я розкриваю набагато кращий спосіб зробити розбиття рядка: 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 з болем на сервері, протестуйте його

Налаштування схеми 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

якщо хтось хоче отримати лише одну частину окремого тексту, може використати це

виберіть * відSplitStringSep ('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

Я це присвятив,

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

спираючись на рішення @NothingsImpossible, або, скоріше, прокоментував найвідповідальнішу відповідь (трохи нижче прийнятої), я виявив, що таке швидке та брудне рішення відповідає моїм власним потребам - це має перевагу бути виключно в домені SQL.

задавши рядок "перший; другий; третій; четвертий; п'ятий", скажімо, я хочу отримати третій маркер. це працює лише в тому випадку, якщо ми знаємо, скільки жетонів буде мати рядок - в цьому випадку це 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

це працює лише в тому випадку, якщо ми знаємо, скільки токенів буде мати струна - обмежувальне обмеження ...
Шнуго

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-го результату.
Джонні Карр

STRING_SPLITне гарантує повернення того ж замовлення. Але OPENJSONтак (див. Мою відповідь (розділ оновлення) )
Шнуго,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.