Как разбить строку, чтобы получить доступ к элементу x?

Как с помощью SQL Server разбить строку, чтобы получить доступ к элементу x?

Возьмите строку «Привет, Джон Смит». Как я могу разделить строку по пробелу и получить доступ к элементу с индексом 1, который должен вернуть «Джон»?

См. Также stackoverflow.com/questions/314824/…

Jarrod Dixon 08.03.2010 22:44

встроенный с sql server 2016 msdn.microsoft.com/en-us/library/mt684588.aspx

Tim Abell 24.06.2016 13:02

Ответы наибольший здесь - по крайней мере, для меня - довольно старомодны и довольно устаревшие. Процедурный указатель, циклы, рекурсии, CLR, функции, много строк кода ... Было бы интересно прочитать "активные" ответы, чтобы найти больше подходов своевременно.

Shnugo 12.07.2016 13:18

Я добавил новый ответ с более современным подходом: stackoverflow.com/a/49669994/632604

Gorgi Rankovski 05.04.2018 13:25

Попробуйте Получить n-й элемент списка -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista

José Diz 03.09.2019 23:45

Некоторые комментарии (в том числе и мой) были удалены. Кто-нибудь из модераторов хочет прокомментировать?

Dave Mason 18.04.2020 22:40

@TimAbell, в документации указано, что «Порядок не гарантированно соответствует порядку подстрок во входной строке».

Greg 06.01.2021 19:33
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
502
7
863 185
43
Перейти к ответу Данный вопрос помечен как решенный

Ответы 43

Ответ принят как подходящий

Вы можете найти решение в Пользовательская функция SQL для анализа строки с разделителями полезным (от Кодовый проект).

Вы можете использовать эту простую логику:

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

почему 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)?

Beth 29.09.2010 19:13

@GateKiller Это решение не поддерживает Unicode и использует жестко заданные числа (18,3), что не делает его жизнеспособной функцией "многократного использования".

Filip De Vos 18.03.2011 16:55

Это работает, но выделяет много памяти и расходует ресурсы ЦП.

jjxtra 26.05.2015 19:56

Начиная с SQL Server 2016, теперь есть встроенная функция STRING_SPLIT, которая разбивает строку и возвращает результат таблицы с одним столбцом, который вы можете использовать в инструкции SELECT или где-либо еще.

qJake 03.04.2017 23:24

Жаль, что парни, с которыми я работаю, не в 2016 году. Но я буду иметь это в виду на случай, если они когда-нибудь вырвутся вперед из своей обуви. Отличное решение тем временем. Я реализовал это как функцию и добавил разделитель в качестве аргумента.

Brandon Griffin 02.05.2017 22:38

@Jonesinator Спасибо. Это мне второй раз, когда я приезжаю сюда. И, наконец, сделали функцию со значением таблицы SplitString с этим для сервера Sql 2008, 2010, 2014 Здесь функция SplitString и ее механизм вызова

Muhammad Ashikuzzaman 19.11.2018 19:06

@qJake Как бы вы использовали STRING_SPLIT с индексом? Вопрос спрашивает, как получить конкретное значение, а не полный список.

Stevoisiak 05.08.2020 19:25

Вы можете мне помочь с тем, как изменить эту логику, если input = | 1 | 20 | 3 | 343 | 44 | 6 | 8765 |

aditya 06.10.2020 08:42

Ну, у меня не все так просто, но вот код, который я использую, чтобы разделить входную переменную с разделителями-запятыми на отдельные значения и поместить их в переменную таблицы. Я уверен, что вы могли бы немного изменить это, чтобы разделить на основе пробела, а затем выполнить базовый запрос SELECT для этой табличной переменной, чтобы получить свои результаты.

-- Create temporary table to parse the list of accounting cycles.
DECLARE @tblAccountingCycles table
(
    AccountingCycle varchar(10)
)

DECLARE @vchAccountingCycle varchar(10)
DECLARE @intPosition int

SET @vchAccountingCycleIDs = LTRIM(RTRIM(@vchAccountingCycleIDs)) + ','
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)

IF REPLACE(@vchAccountingCycleIDs, ',', '') <> ''
BEGIN
    WHILE @intPosition > 0
    BEGIN
        SET @vchAccountingCycle = LTRIM(RTRIM(LEFT(@vchAccountingCycleIDs, @intPosition - 1)))
        IF @vchAccountingCycle <> ''
        BEGIN
            INSERT INTO @tblAccountingCycles (AccountingCycle) VALUES (@vchAccountingCycle)
        END
        SET @vchAccountingCycleIDs = RIGHT(@vchAccountingCycleIDs, LEN(@vchAccountingCycleIDs) - @intPosition)
        SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
    END
END

Концепция почти такая же. Еще одна альтернатива - использовать совместимость .NET в самом SQL Server 2005. По сути, вы можете написать себе простой метод в .NET, который разделит строку, а затем предоставит ее как хранимую процедуру / функцию.

пример выполнения этого в .NET (процедуры / функции CLR) можно найти здесь cstruter.com/blog/260

cstruter 28.01.2011 08:47

Вот 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 * from dbo.ethos_SplitString_fn ('парень, фитиль, был здесь', ',') часть идентификатора ----------- ------------ -------------------------------------- 1 парень 2 фитиля

Guy 20.10.2008 19:25

будьте осторожны с len (), так как он не вернет правильное число, если его аргумент имеет конечные пробелы., например len ('-') = 2.

Rory 17.10.2009 20:30

Не работает на: select * from dbo.SplitString ('foo, foo test ,,,, foo', ',')

cbp 14.04.2010 09:14

Исправление для cbp. Выберите @myString = substring (@ mystring, @ iSpaces + len (@deliminator), len (@myString) - charindex (@ deliminator, @ myString, 0))

Alxwest 21.05.2012 14:12

Попробуй это:

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 15.09.2010 10:57

Я не верю, что в SQL Server есть встроенная функция разделения, поэтому, кроме UDF, единственный известный мне ответ - захватить функцию PARSENAME:

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

PARSENAME берет строку и разбивает ее на символ точки. Он принимает число в качестве второго аргумента, и это число указывает, какой сегмент строки следует вернуть (работая от конца к началу).

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

Очевидная проблема - когда строка уже содержит точку. Я все еще думаю, что использование UDF - лучший способ ... есть другие предложения?

Спасибо, Сол ... Я должен отметить, что это решение действительно плохое решение для реальной разработки. PARSENAME ожидает только четыре части, поэтому использование строки с более чем четырьмя частями приводит к возврату NULL. Решения UDF явно лучше.

Nathan Bedford 01.07.2009 19:54

Это отличный хакер, и он заставляет меня плакать, что что-то подобное необходимо для чего-то такого чертовски простого на реальных языках.

Factor Mystic 12.07.2010 18:09

Чтобы индексы работали «правильным» образом, то есть начиная с 1, я перехватил ваш взлом с помощью REVERSE: REVERSE (PARSENAME (REPLACE (REVERSE ('Hello John Smith'), '', '.') , 1)) - Возвращает Hello

NothingsImpossible 14.05.2012 17:57

@FactorMystic Первая нормальная форма требует, чтобы вы не помещали несколько значений в одно поле. Это буквально первое правило СУБД. Функция SPLIT() не предоставляется, поскольку она способствует плохому проектированию базы данных, и база данных никогда не будет оптимизирована для использования данных, хранящихся в этом формате. РСУБД не обязана помогать разработчикам делать глупые вещи, для обработки которых она предназначена нет. Правильный ответ будет всегда: «Нормализовать вашу базу данных, как мы говорили вам 40 лет назад». Ни SQL, ни СУБД не виноваты в плохом дизайне.

Bacon Bits 08.12.2014 07:11

Это супер круто. Так уж получилось, что данные, с которыми я работаю, состоят из четырех элементов.

MrBliz 25.06.2015 14:57

+1 всегда может придумать быстрое и грязное рабочее решение для> 4 токенов с комбинацией reverse (), charindex (), substring ()

hello_earth 08.02.2016 12:09

@BaconBits, хотя теоретически я согласен, на практике такие инструменты полезны при нормализации плохого дизайна, созданного кем-то, кто был до вас.

Tim Abell 24.06.2016 12:34

Когда количество разбиений в строке меняется слева направо, тогда путь @NothingsImpossible - это правильный путь.

wwmbes 27.10.2016 20:14

@NothingsImpossible Производительность решения REVERSE (PARSENAME (REPLACE (REVERSE ('Hello John Smith'), '', '.'), 1)) превосходна, и ограничение на 4 поля подходит для моей проблемы. Спасибо!!!

wwmbes 28.10.2016 10:23

@hello_earth Мне бы хотелось увидеть, что вы придумали. Пожалуйста, покажите нам.

wwmbes 28.10.2016 10:29

@wwmbes Я опубликовал отдельный ответ - он очень быстрый, грязный и некрасивый, и основан на тех же принципах, но в любом случае, поскольку вы спросили. ваше здоровье

hello_earth 31.10.2016 17:20

Для осведомленности: поскольку функция parsename предназначена для возврата идентификаторов базы данных, возвращаемое значение ограничено 128 символами (тип данных sysname, который соответствует nvarchar (128)). Если это выходит за рамки этого, вместо этого будет возвращено NULL.

Kyle Weller 30.05.2017 23:31

Для осведомленности: он работает только с четырьмя элементами или меньше. Если у вас более четырех элементов, PARSENAME всегда возвращает NULL, даже для индекса ниже 5. docs.microsoft.com/it-it/sql/t-sql/functions/…

aKiRa 24.05.2018 16:22

Сначала создайте функцию (с использованием 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

Использование остается прежним.

Это элегантно, но работает только для 100 элементов из-за ограничения глубины рекурсии.

Pking 07.11.2012 19:31

@Pking, нет, значение по умолчанию - 100 (для предотвращения бесконечного цикла). Используйте Подсказка MAXRECURSION, чтобы определить количество уровней рекурсии (от 0 до 32767, 0 - «без ограничений» - может привести к сбою сервера). Кстати, гораздо лучший ответ, чем PARSENAME, потому что он универсален :-). +1

Michał Powaga 14.03.2013 18:45

Добавляя maxrecursion к этому решению, имейте в виду этот вопрос и его ответы Как настроить параметр maxrecursion для CTE внутри функции с табличным значением.

Michał Powaga 15.03.2013 13:03

В частности, обратитесь к ответ Crisfole - его метод несколько замедляет его, но он проще, чем большинство других вариантов.

AHiggins 30.07.2015 21:05

второстепенный момент, но использование не остается прежним, потому что вы изменили имя столбца, поэтому s больше не определен

Tim Abell 24.06.2016 12:45

Я избегаю ограничения рекурсии, используя этот код, который, если бы я не перешел на SQL Server 2016, я бы сделал функцию с табличным значением или скалярную функцию, в зависимости от моих реальных потребностей. Путем зацикливания вместо самосоединения теоретически можно использовать строку любого размера.

Joey Morgan 12.02.2020 00:15

Вы можете использовать числовую таблицу для синтаксического анализа строк.

Создайте таблицу физических номеров:

    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]

Производительность здесь не впечатляет, но вызов функции с таблицей из миллиона строк - не лучшая идея. При выполнении разбиения строки на несколько строк я бы избегал этой функции.

Лучшее решение IMO, у других есть какое-то ограничение .. это быстро и может анализировать длинные строки со многими элементами.

Pking 06.12.2012 17:01

Почему вы заказываете n по убыванию? Если там было три элемента, и мы начали нумерацию с 1, то первый элемент будет под номером 3, а последний - под номером 1. Разве это не дало бы более интуитивных результатов, если бы desc был удален?

hatchet - done with SOverflow 28.10.2014 19:13

Согласен, было бы по возрастанию нагляднее. Я следовал соглашению parsename (), в котором используется desc

Nathan Skerl 28.10.2014 20:43

какое-то объяснение того, как это работает, было бы здорово

Tim Abell 24.06.2016 12:46

В тесте со 100 миллионами строк до 3 полей для анализа ufn_ParseArray не завершился через 25 минут, а REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) из @NothingsImpossible завершился за 1,5 минуты. @hello_earth Как ваше решение будет сравниваться с более длинными строками с более чем 4 полями?

wwmbes 28.10.2016 10:41

При дальнейшем исследовании, когда версия @NothingsImpossible встроена в функцию и используется оттуда, она работает в 25 раз хуже, чем при использовании непосредственно в запросе. Кто-нибудь может прокомментировать, почему?

wwmbes 28.10.2016 19:23

@wwmbes Попробуйте использовать таблицу физических номеров с кластеризованным индексом. Использование master..spt_values ​​только для иллюстрации

Nathan Skerl 28.10.2016 20:49

@wwmbes добавил пример таблицы физических номеров

Nathan Skerl 28.10.2016 21:06

Я искал решение в сети, и приведенное ниже работает для меня. Ссылка.

И вы вызываете функцию так:

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-му элементу с помощью этой функции.

Björn Lindqvist 03.10.2014 13:21

На мой взгляд, вы, ребята, слишком усложняете задачу. Просто создайте 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()));
  }
};

Я думаю, это слишком сложно, потому что мне нужно иметь Visual Studio, затем включить CLR на сервере, затем создать и скомпилировать проект и, наконец, добавить сборки в базу данных, чтобы использовать его. Но все же это интересный ответ.

Guillermo Gutiérrez 27.09.2012 17:55

@ guillegr123, это не должно быть сложно. Вы можете просто скачать и установить (бесплатно!) SQL #, который представляет собой библиотеку функций и процедур SQLCLR. Вы можете получить его от SQLsharp.com. Да, я являюсь автором, но String_Split включен в бесплатную версию.

Solomon Rutzky 18.08.2013 20:18

Я знаю, что это старый вопрос, но я думаю, что кто-то может извлечь выгоду из моего решения.

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

Преимущества:

  • Он разделяет все 3 разделителя подстроки символом "".
  • Нельзя использовать цикл while, так как он снижает производительность.
  • Нет необходимости в повороте, так как вся результирующая подстрока будет отображаться в одна строка

Ограничения:

  • Нужно знать общее количество. пробелов (подстрока).

Примечание: решение может выдавать подстроку до N.

Чтобы преодолеть ограничение, мы можем использовать следующий ссылка.

Но снова вышеуказанный решение нельзя использовать в таблице (на самом деле я не мог его использовать).

Опять же, я надеюсь, что это решение может кому-то помочь.

Обновлять: В случае записей> 50000, желательно не будет использовать LOOPS, так как это ухудшит Спектакль

Здесь я публикую простой способ решения

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

Мне понравилось это решение. Расширен, чтобы вернуть скалярное значение на основе указанного столбца в результатах.

Alan 23.02.2013 02:21

Я получил ожог с '&' в строке, которую нужно разделить с помощью этого

KeithL 13.11.2018 19:16

А как насчет использования 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

Я использовал ваш ответ, но не работал, но я изменил, и это сработало с union all, я использую sql 2005

angel 13.08.2013 19:06

В следующем примере используется рекурсивный 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

Вот мое решение, которое может кому-то помочь. Модификация ответа Jonesinator выше.

Если у меня есть строка значений INT с разделителями и я хочу, чтобы была возвращена таблица значений INT (к которой я затем могу присоединиться). например '1,20,3,343,44,6,8765'

Создайте UDF:

IF OBJECT_ID(N'dbo.ufn_GetIntTableFromDelimitedList', N'TF') IS NOT NULL
    DROP FUNCTION dbo.[ufn_GetIntTableFromDelimitedList];
GO

CREATE FUNCTION dbo.[ufn_GetIntTableFromDelimitedList](@String NVARCHAR(MAX),                 @Delimiter CHAR(1))

RETURNS @table TABLE 
(
    Value INT NOT NULL
)
AS 
BEGIN
DECLARE @Pattern NVARCHAR(3)
SET @Pattern = '%' + @Delimiter + '%'
DECLARE @Value NVARCHAR(MAX)

WHILE LEN(@String) > 0
    BEGIN
        IF PATINDEX(@Pattern, @String) > 0
        BEGIN
            SET @Value = SUBSTRING(@String, 0, PATINDEX(@Pattern, @String))
            INSERT INTO @table (Value) VALUES (@Value)

            SET @String = SUBSTRING(@String, LEN(@Value + @Delimiter) + 1, LEN(@String))
        END
        ELSE
        BEGIN
            -- Just the one value.
            INSERT INTO @table (Value) VALUES (@String)
            RETURN
        END
    END

RETURN
END
GO

Затем получите результаты таблицы:

SELECT * FROM dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',')

1
20
3
343
44
6
8765

И в заявлении соединения:

SELECT [ID], [FirstName]
FROM [User] u
JOIN dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',') t ON u.[ID] = t.[Value]

1    Elvis
20   Karen
3    David
343  Simon
44   Raj
6    Mike
8765 Richard

Если вы хотите вернуть список NVARCHAR вместо INT, просто измените определение таблицы:

RETURNS @table TABLE 
(
    Value NVARCHAR(MAX) NOT NULL
)

Я использую ответ Фредерика, но это не сработало в 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. 24.03.2016 11:36

Я был очень взволнован, когда увидел это, потому что он выглядел настолько чистым и легким для понимания, но, к сожалению, вы не можете поместить это в UDF из-за EXEC. EXEC неявно вызывает хранимую процедуру, и вы не можете использовать хранимые процедуры в UDF.

Kristen Hammack 10.08.2016 16:20

Это отлично работает !! Я искал возможность использовать функцию (SplitStrings_Moden) отсюда: sqlperformance.com/2012/07/t-sql-queries/split-strings#comme‌ nts, которая делает это, и потребовалось полторы минуты, чтобы разделить данные и вернуть строки при использовании только 4 номеров счетов. Я протестировал вашу версию с левым соединением в таблице с данными о номерах счетов, и это заняло примерно 2 или 3 секунды! Огромная разница и работает безотказно! Если можно, я бы отдал за это 20 голосов!

MattE 08.03.2019 21:57



    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 &LT 0 Then @Temp Else Left(@Temp, @End) End,
             -- New @Expression
             @Expression = REPLACE(@Expression,
                                   @Qualifier + @Temp2 + Case When @End &LT 0 Then '' Else @Qualifier End,
                                   Replace(@Temp2, @Delimiter, '|||***|||')
                           )
       END

       -- Replace all occurences of @Delimiter within @Expression with '&lt/fn_Split&gt&ltfn_Split&gt'
       -- And convert it to XML so we can select from it
       SET
          @X = Cast('&ltfn_Split&gt' +
                    Replace(@Expression, @Delimiter, '&lt/fn_Split&gt&ltfn_Split&gt') +
                    '&lt/fn_Split&gt' 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

В большинстве решений здесь используются циклы while или рекурсивные 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. Я попросил сообщества помочь в улучшении встроенной функции здесь:

Имея достаточную обратную связь качественный, они действительно могут рассмотреть возможность внесения некоторых из этих улучшений:

Подробнее о функциях разделения, почему (и доказательство того, что) циклы while и рекурсивные CTE не масштабируются, а также о лучших альтернативах при разделении строк, поступающих с уровня приложения:

Однако в SQL Server 2016 или более поздней версии вы должны смотреть на STRING_SPLIT() и STRING_AGG():

Лучший ответ, ИМХО. В некоторых других ответах есть проблема ограничения рекурсии SQL 100, но не в этом случае. Очень быстрая и очень простая реализация. Где кнопка +2?

T-moty 21.10.2015 18:01

Спасибо Аарону; Но может ли кто-нибудь объяснить, почему, если я передаю varchar (не varchar(max)) в качестве аргумента, эта функция возвращает пустой список? как declare @list varchar = 'something'; select from dbo.SplitString(@list, ';');

Mikhail Boyarsky 23.09.2016 16:20

@Mikhail Потому что varchar без длины может быть varchar(30) или varchar(1) в зависимости от контекста. Не пытайтесь понять эту проблему - просто не используйте этот синтаксис. Всегда.

Aaron Bertrand 23.09.2016 23:08

Я попробовал эту функцию дословно с использованием: select * from DBO.SplitString('Hello John smith', ' ');, и на выходе получилось: Ценить Hello ello llo lo o John ohn hn n smith mith th h

wwmbes 11.10.2016 13:27

@wwmbes Попробуйте использовать разделитель, отличный от пробела. Многие функции будут иметь проблему с отбрасыванием конечных пробелов.

Aaron Bertrand 11.10.2016 18:07

@AaronBertrand Исходная проблема, опубликованная GateKiller, включает в себя разделитель пробелов.

wwmbes 26.10.2016 15:00

Функция "len" имеет неявный встроенный "rtrim", который появился из 80-х, когда в SQL был только char (не varchar), поэтому попробуйте следующее: select len( ' ' ) as len_space, len( '* ' ) as len_star_space, len( ' *' ) as len_space_star, datalength( ' ' ) as datalength_space, datalength( '* ' ) as datalength_star_space, datalength( ' *' ) as datalength_space_star, case when '' = ' ' then 'yes' else 'no' end as nospace_equals_space Исправление: замените "len" на "datalength" (но будьте осторожны, n (var) символ)

Alasdair C-S 22.10.2019 16:20

Это разбивает строку. как получить конкретный n-й элемент?

user1255933 14.12.2019 02:48

@ user1255933 Адресовано.

Aaron Bertrand 18.12.2019 00:01

@AaronBertrand Большое спасибо, я могу воспроизвести это.

user1255933 19.12.2019 22:43

@AaronBertrand пример добавления для применения ко всей таблице: SELECT o.myfield, u.value из mytable o cross apply dbo.SplitString (o.myfield, ';') u, где u.idx = 4

user1255933 19.12.2019 23:17

Это не сработает, если у вас нет разрешения «СОЗДАТЬ ФУНКЦИЮ» ...

Michael 27.02.2020 02:58

@ Майкл Да, это правда. У вас также не будет таблицы для выбора, если у вас нет разрешения ALTER SCHEMA, и вы не сможете выбирать из нее, если у вас нет разрешения SELECT. Вы всегда можете Спроси кого-нибудь создать функцию для вас. Или создайте его где-нибудь, где сможете (даже временно, например, в tempdb). И в 2016+ вы должны использовать STRING_SPLIT (), а не функцию, которую вы все равно должны создать.

Aaron Bertrand 27.02.2020 03:19

@AaronBertrand Я нахожусь в 2016 году, но, по-видимому, не уровень совместимости> = 130 (администратор базы данных не был уверен и не сможет изучить это позже)

Michael 27.02.2020 06:41

@AaronBertrand "А в 2016+ вам следует использовать STRING_SPLIT ()": я не могу представить себе какое-либо реальное использование STRING_SPLIT, поскольку он не возвращает индекс для частей строки, которые были разделены, и согласно это документы , «порядок вывода может отличаться, поскольку порядок не гарантированно соответствует порядку подстрок во входной строке».

Reversed Engineer 08.04.2020 17:30

Мне нравится, как этот элемент обратной связи Azure выражает это: «STRING_SPLIT не является полной функцией» и «Жалко, что это было отправлено как просто« предложение ». Фактически это должно быть указано как «ошибка», потому что существует лишь сравнительно небольшой набор вариантов использования, в которых перечисление результирующего набора элементов не имеет значения ».

Reversed Engineer 08.04.2020 17:35

@ReversedEngineer Существует множество вариантов использования, которые не заботятся (и не должны) относиться к исходному порядку в списке. «Найдите всех клиентов в этом списке». Почему это важно, если в списке есть 3,5,2,8 или 8,3,5,2? Я думаю, что это довольно непонятный крайний случай, когда требование было бы «Найти всех клиентов в этом списке, и отобразит их именно в этом порядке». (Я не возражаю против исправления этого, я просто предполагаю, что мое впечатление о большинстве случаев использования функции отличается от вашего. Это неофициальное свидетельство из числа принятых ответов, показывающих функции без порядкового номера.)

Aaron Bertrand 08.04.2020 19:44

@ReversedEngineer В любом случае обновил свой ответ с помощью некоторые из моих мыслей по этому поводу. Кроме того, я был одним из тех, кто настаивал на ясности в документации, что порядок вывода может не совпадать с порядком ввода.

Aaron Bertrand 08.04.2020 19:54

@AaronBertrand «Кроме того, я был одним из тех, кто настаивал на ясности в документации, что порядок вывода может не совпадать с порядком ввода». - Спасибо! Я ценю то, что вы делаете, чтобы «требовать от Microsoft улучшений», а также помогаете прояснить эту расплывчатую «документацию Microsoft для чайников». Также я понимаю вашу точку зрения о том, что бывают случаи, когда порядок не имеет значения, и в этом случае STRING_SPLIT действительно отличное решение.

Reversed Engineer 09.04.2020 12:32

Рекурсивное решение 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 |

Вот SQL UDF, который может разбить строку и захватить только определенный фрагмент.

create FUNCTION [dbo].[udf_SplitParseOut]
(
    @List nvarchar(MAX),
    @SplitOn nvarchar(5),
    @GetIndex smallint
)  
returns varchar(1000)
AS  

BEGIN

DECLARE @RtnValue table 
(

    Id int identity(0,1),
    Value nvarchar(MAX)
) 


    DECLARE @result varchar(1000)

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

    select @result = value from @RtnValue where ID = @GetIndex

    Return @result
END

Простой оптимизированный алгоритм:

ALTER FUNCTION [dbo].[Split]( @Text NVARCHAR(200),@Splitor CHAR(1) )
RETURNS @Result TABLE ( value NVARCHAR(50)) 
AS
BEGIN
    DECLARE @PathInd INT
    Set @Text+=@Splitor
    WHILE LEN(@Text) > 0
    BEGIN
        SET @PathInd=PATINDEX('%'+@Splitor+'%',@Text)
        INSERT INTO  @Result VALUES(SUBSTRING(@Text, 0, @PathInd))
        SET @Text= SUBSTRING(@Text, @PathInd+1, LEN(@Text))
    END
        RETURN 
END

Я использовал ответ vzczc с использованием рекурсивных cte в течение некоторого времени, но хотел обновить его для обработки разделителя переменной длины, а также для обработки строк с ведущими и запаздывающими «разделителями», например, когда у вас есть файл csv с такими записями, как :

«Боб», «Смит», «Саннивейл», «Калифорния»

или когда вы имеете дело с шестью частями fqn, как показано ниже. Я широко использую их для ведения журнала subject_fqn для аудита, обработки ошибок и т.д., а parsename обрабатывает только четыре части:

[netbios_name].[machine_name].[instance].[database].[schema].[table].[column]

Вот моя обновленная версия и спасибо vzczc за его оригинальный пост!

select * from [utility].[split_string](N'"this"."string"."gets"."split"."and"."removes"."leading"."and"."trailing"."quotes"', N'"."', N'"', N'"');

select * from [utility].[split_string](N'"this"."string"."gets"."split"."but"."leaves"."leading"."and"."trailing"."quotes"', N'"."', null, null);

select * from [utility].[split_string](N'[netbios_name].[machine_name].[instance].[database].[schema].[table].[column]', N'].[', N'[', N']');

create function [utility].[split_string] ( 
  @input       [nvarchar](max) 
  , @separator [sysname] 
  , @lead      [sysname] 
  , @lag       [sysname]) 
returns @node_list table ( 
  [index]  [int] 
  , [node] [nvarchar](max)) 
  begin 
      declare @separator_length [int]= len(@separator) 
              , @lead_length    [int] = isnull(len(@lead), 0) 
              , @lag_length     [int] = isnull(len(@lag), 0); 
      -- 
      set @input = right(@input, len(@input) - @lead_length); 
      set @input = left(@input, len(@input) - @lag_length); 
      -- 
      with [splitter]([index], [starting_position], [start_location]) 
           as (select cast(@separator_length as [bigint]) 
                      , cast(1 as [bigint]) 
                      , charindex(@separator, @input) 
               union all 
               select [index] + 1 
                      , [start_location] + @separator_length 
                      , charindex(@separator, @input, [start_location] + @separator_length) 
               from   [splitter] 
               where  [start_location] > 0) 
      -- 
      insert into @node_list 
                  ([index],[node]) 
        select [index] - @separator_length                   as [index] 
               , substring(@input, [starting_position], case 
                                                            when [start_location] > 0 
                                                                then 
                                                              [start_location] - [starting_position] 
                                                            else 
                                                              len(@input) 
                                                        end) as [node] 
        from   [splitter]; 
      -- 
      return; 
  end; 
go 

Почти все другие ответы заменяют разделяемую строку, которая тратит циклы ЦП и выполняет ненужное выделение памяти.

Я описываю гораздо лучший способ разбить строку здесь: 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.

Этот шаблон отлично работает, и вы можете обобщить

Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[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 минут (сервер aws m3, 4k iops подготовленный диск)

Andrew Hill 08.12.2014 06:53

Хотя это похоже на ответ на основе 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

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

Решение на основе чистого набора с использованием 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

если кто-то хочет получить только одну часть отложенного текста, может использовать это

выберите * from 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
  )

Я оставил это,

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 всегда должен быть там.

Вы можете разделить строку в 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); 

Еще одна функция получения n-й части строки с помощью функции-разделителя:

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 30.03.2016 00:16

Это вопрос не о подходе с разделением строк, но про как получить n-й элемент.

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

Но - если вы действительно не хочу ничего, кроме подхода к получению 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 в своей строке, чтобы неявно заменить все запрещенные символы соответствующей escape-последовательностью.

Это особый случай, если - дополнительно - ваш разделитель - точка с запятой. В этом случае я сначала заменяю разделитель на '# 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-Server 2016+, есть JSON_VALUE и OPENJSON.

С JSON_VALUE мы можем передать позицию как индексный массив.

Для OPENJSON в документация четко указано:

When OPENJSON parses a JSON array, the function returns the indexes of the elements in the JSON text as keys.

Строке вроде 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>.

Salman A 28.01.2019 10:26

@SalmanA, да, CDATA-разделы тоже могут справиться с этим ... Но после преобразования они исчезли (неявно изменены на экранированный text()). Мне не нравится магия под капотом, поэтому я бы предпочел подход (SELECT 'Text with <&>' AS [*] FOR XML PATH('')). Мне это кажется более чистым и все равно происходит ... (Еще немного о CDATA и XML).

Shnugo 28.01.2019 10:32

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

опираясь на решение @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
это работает, только если мы знаем, сколько токенов будет в строке - a breaking limitation...
Shnugo 09.04.2018 11:42

Начиная с SQL Server 2016 мы string_split

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

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

Это хорошо, но не решает вопрос о получении n-го результата.

Johnie Karr 28.12.2017 16:47

STRING_SPLIT не гарантирует возврат того же заказа. Но OPENJSON делает (см. Мой ответ (раздел обновления))

Shnugo 14.09.2018 10:05

Современный подход с использованием STRING_SPLIT требует SQL Server 2016 и выше.

DECLARE @string varchar(100) = 'Hello John Smith'

SELECT
    ROW_NUMBER() OVER (ORDER BY value) AS RowNr,
    value
FROM string_split(@string, ' ')

Результат:

RowNr   value
1       Hello
2       John
3       Smith

Теперь можно получить n-й элемент из номера строки.

STRING_SPLIT не гарантирует возврат того же заказа. Но OPENJSON делает (см. Мой ответ (раздел обновления))

Shnugo 14.09.2018 10:04

Ответ Аарона Бертрана великолепен, но ошибочен. Он не точно обрабатывает пробел в качестве разделителя (как это было в примере в исходном вопросе), поскольку функция длины удаляет конечные пробелы.

Ниже приведен его код с небольшой поправкой, позволяющей использовать разделитель пробелов:

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

Если ваша база данных имеет уровень совместимости 130 или выше, вы можете использовать функцию STRING_SPLIT вместе с предложениями СМЕЩЕНИЕ ВЫВОДА, чтобы получить конкретный элемент по индексу.

Чтобы получить элемент по индекс 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 09.04.2018 11:35

Хорошо, не использовал это раньше ... Приятно знать ... Я все же предпочел бы подход, основанный на разделении xml, поскольку он позволяет извлекать значение типобезопасно и не требует подзапроса, но это хороший. +1 с моей стороны

Shnugo 09.04.2018 11:40

проблема здесь в том, что STRING_SPLIT не гарантирует порядок возвращаемых результатов. Таким образом, ваш предмет 1 может быть или не быть моим предметом 1.

user1443098 08.04.2019 16:49

@GorgiRankovski, Использование требований STRING_SPLIT для v2016 +. В этом случае лучше использовать OPENJSON или JSON_VALUE. Вы можете захотеть проверь мой ответ

Shnugo 01.07.2019 11:08

Вот функция, которая выполнит задачу по разделению строки и доступу к элементу X:

CREATE FUNCTION [dbo].[SplitString]
(
   @List       VARCHAR(MAX),
   @Delimiter  VARCHAR(255),
   @ElementNumber INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN

       DECLARE @inp VARCHAR(MAX)
       SET @inp = (SELECT REPLACE(@List,@Delimiter,'_DELMTR_') FOR XML PATH(''))

       DECLARE @xml XML
       SET @xml = '<split><el>' + REPLACE(@inp,'_DELMTR_','</el><el>') + '</el></split>'

       DECLARE @ret VARCHAR(MAX)
       SET @ret = (SELECT
              el = split.el.value('.','varchar(max)')
       FROM  @xml.nodes('/split/el[string-length(.)>0][position() = sql:variable("@elementnumber")]') split(el))

       RETURN @ret

END

Использование:

SELECT dbo.SplitString('Hello John Smith', ' ', 2)

Результат:

John

Это слишком сложно ... .nodes() не нужен. Вы можете поместить XQuery непосредственно в .value() (см. Мой ответ). Кстати: скалярные функции очень плохие исполнители. Намного лучше было встроенный TVF, даже если он возвращает только одну ячейку в одной строке ...

Shnugo 29.06.2018 12:32

ПРОСТОЕ РЕШЕНИЕ ДЛЯ РАЗБОРКИ ПО ПЕРВОЕ И ПОСЛЕДНЕЕ ИМЯ

DECLARE @Name varchar(10) = 'John Smith'

-- Get First Name
SELECT SUBSTRING(@Name, 0, (SELECT CHARINDEX(' ', @Name)))

-- Get Last Name
SELECT SUBSTRING(@Name, (SELECT CHARINDEX(' ', @Name)) + 1, LEN(@Name))

В моем случае (и во многих других, кажется ...) у меня есть список имен и фамилий, разделенных одним пробелом. Это можно использовать непосредственно внутри оператора select для анализа имени и фамилии.

-- i.e. Get First and Last Name from a table of Full Names
SELECT SUBSTRING(FullName, 0, (SELECT CHARINDEX(' ', FullName))) as FirstName,
SUBSTRING(FullName, (SELECT CHARINDEX(' ', FullName)) + 1, LEN(FullName)) as LastName,
From FullNameTable

Я знаю, что это поздно, но недавно у меня было это требование, и я придумал приведенный ниже код. У меня нет возможности использовать функцию, определяемую пользователем. Надеюсь это поможет.

SELECT 
    SUBSTRING(
                SUBSTRING('Hello John Smith' ,0,CHARINDEX(' ','Hello John Smith',CHARINDEX(' ','Hello John Smith')+1)
                        ),CHARINDEX(' ','Hello John Smith'),LEN('Hello John Smith')
            )

Я понимаю, что это действительно старый вопрос, но, начиная с SQL Server 2016, есть функции для анализа данных JSON, которые можно использовать для конкретного решения вопроса OP - и без разделения строк или использования определяемой пользователем функции. Чтобы получить доступ к элементу по определенному индексу в строке с разделителями, используйте функцию JSON_VALUE. Однако требуются правильно отформатированные данные JSON: строки должны быть заключены в двойные кавычки ", а разделителем должна быть запятая ,, а вся строка должна быть заключена в квадратные скобки [].

DECLARE @SampleString NVARCHAR(MAX) = '"Hello John Smith"';
--Format as JSON data.
SET @SampleString = '[' + REPLACE(@SampleString, ' ', '","') + ']';
SELECT 
    JSON_VALUE(@SampleString, '$[0]') AS Element1Value,
    JSON_VALUE(@SampleString, '$[1]') AS Element2Value,
    JSON_VALUE(@SampleString, '$[2]') AS Element3Value;

Выход

Element1Value         Element2Value       Element3Value
--------------------- ------------------- ------------------------------
Hello                 John                Smith

(1 row affected)

Использование SQL Server 2016 и выше. Используйте этот код для ОБРЕЗКИ строк, игнорируйте значения NULL и применяйте индекс строки в правильном порядке. Он также работает с разделителем пробелов:

DECLARE @STRING_VALUE NVARCHAR(MAX) = 'one, two,,three, four,     five'

SELECT ROW_NUMBER() OVER (ORDER BY R.[index]) [index], R.[value] FROM
(
    SELECT
        1 [index], NULLIF(TRIM([value]), '') [value] FROM STRING_SPLIT(@STRING_VALUE, ',') T
    WHERE
        NULLIF(TRIM([value]), '') IS NOT NULL
) R

Если вы просмотрите следующий учебник SQL по разделение строки с помощью SQL, вы найдете ряд функций, которые можно использовать для разделения заданной строки на SQL Server.

Например, UDF-функцию SplitAndReturnNth можно использовать для разделения текста с помощью разделителя и возврата N-го фрагмента в качестве вывода функции.

select dbo.SplitAndReturnNth('Hello John Smith',' ',2)

Другие вопросы по теме