Как разделить строку, чтобы получить доступ к элементу x? - PullRequest
472 голосов
/ 05 августа 2008

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

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

Ответы [ 44 ]

350 голосов
/ 05 августа 2008

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

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

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

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

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

183 голосов
/ 05 августа 2008

Вы можете найти решение в Пользовательская функция 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
108 голосов
/ 05 августа 2008

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

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

56 голосов
/ 12 ноября 2013

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

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)) = @Delim
          ) AS y
        );

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

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

37 голосов
/ 27 октября 2008

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

Создать таблицу физических чисел:

    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)

* 1015 очистки *

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

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

23 голосов
/ 08 июля 2016

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

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

Но - если вы действительно не хотите ничего, кроме подхода к получению 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+, есть 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';

SELECT [value]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]')
WHERE [key]=1 --zero-based!
21 голосов
/ 05 августа 2008

Вот 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','**')
15 голосов
/ 30 января 2013

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

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 голосов
/ 01 марта 2013

Как насчет использования операторов 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
10 голосов
/ 20 июля 2012

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