Разделенная строка T-SQL

142

У меня есть столбец SQL Server 2008 R2, содержащий строку, которую мне нужно разделить запятой. Я видел много ответов на StackOverflow, но ни один из них не работает в R2. Я убедился, что у меня есть права выбора для любых примеров функции разделения. Любая помощь очень ценится.

Ли Гриндон
источник
7
Это один из миллиона ответов, которые мне нравятся stackoverflow.com/a/1846561/227755
nurettin
2
Что значит «ни один из них не работает»? Можете быть более конкретными?
Аарон Бертран
Энди указал мне в правильном направлении, поскольку я неправильно выполнял функцию. Вот почему ни один из других ответов стека не работал. Моя вина.
Ли Гриндон
2
возможный дубликат разделенной строки в SQL
Прахалад Гаггар
В mdq.RegexSplitнадстройке Master Data Services есть функция, которая может помочь. Конечно, стоит изучить .
jpaugh 01

Ответы:

244

Я использовал этот SQL раньше, который может сработать для вас: -

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

и использовать его: -

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')
Энди Робинсон
источник
2
Большое спасибо, Энди. Я сделал небольшое улучшение в вашем скрипте, чтобы функция могла возвращать элемент по определенному индексу в разделенной строке. Это полезно только в ситуациях, когда вы разбираете структуру первого столбца. gist.github.com/klimaye/8147193
CF_Maintainer
1
Я отправил некоторые улучшения (с бэк тестовых случаев) на мою страницу GitHub здесь . Я отправлю его в качестве ответа в этой ветке переполнения стека, когда у меня будет достаточно репутации, чтобы превысить пост «защита»
mpag
8
Хотя это отличный ответ, он устарел ... Следует избегать процедурных подходов (особенно циклов) ... Стоит изучить новые ответы ...
Шнуго 02
1
Это не работает дляselect * from dbo.splitstring('')
Паси Саволайнен
2
Я полностью согласен с @Shnugo. Разветвители петель работают, но ужасно медленно. Что-то вроде этого sqlservercentral.com/articles/Tally+Table/72993 намного лучше. Некоторые другие отличные варианты на основе наборов можно найти здесь. sqlperformance.com/2012/07/t-sql-queries/split-strings
Шон Ланж
63

Кто-нибудь рассматривал подход, более основанный на наборах, вместо рекурсивных CTE и циклов while? Обратите внимание, что эта функция была написана для вопроса, который основан на SQL Server 2008 и запятой в качестве разделителя . В SQL Server 2016 и выше (и на уровне совместимости 130 и выше) STRING_SPLIT()это лучший вариант .

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(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_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

Если вы хотите избежать ограничения длины строки <= количество строк в sys.all_columns(9 980 дюймов в modelSQL Server 2017; намного больше в ваших собственных пользовательских базах данных), вы можете использовать другие подходы для получения чисел, например построение собственной таблицы чисел . Вы также можете использовать рекурсивный CTE в тех случаях, когда вы не можете использовать системные таблицы или создавать свои собственные:

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

Но вам нужно будет добавить OPTION (MAXRECURSION 0)(или MAXRECURSION <longest possible string length if < 32768>) к внешнему запросу, чтобы избежать ошибок с рекурсией для строк> 100 символов. Если это тоже не лучшая альтернатива, см. Этот ответ, как указано в комментариях.

(Кроме того, должен быть разделитель NCHAR(<=1228). Почему все еще не понятно.)

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

Аарон Бертран
источник
1
В этой процедуре есть небольшая ошибка для случая, когда в конце строки будет нулевое значение - например, в '1,2,, 4,' - поскольку окончательное значение не анализируется. Чтобы исправить эту ошибку, выражение «WHERE Number <= LEN (@List)» следует заменить на «WHERE Number <= LEN (@List) + 1».
SylvainL
@SylvainL Думаю, это зависит от того, какое поведение вы хотите. По моему опыту, большинство людей хотят игнорировать любые конечные запятые, поскольку они на самом деле не представляют реальный элемент (сколько копий пустой строки вам нужно)? В любом случае, реальный способ сделать это - если вы перейдете по второй ссылке - в любом случае пошагово возиться с разделением больших уродливых строк в медленном T-SQL.
Аарон Бертран
1
Как вы уже сказали, большинство людей хотят игнорировать любые конечные запятые, но, увы, не все. Я полагаю, что более полным решением было бы добавить параметр, чтобы указать, что делать в этом случае, но мой комментарий - это всего лишь небольшая заметка, чтобы никто не забыл об этой возможности, поскольку во многих случаях это может быть вполне реально.
SylvainL
У меня странное поведение с этой функцией. Если я использую непосредственно строку в качестве параметра - это работает. Если у меня есть varchar, это не так. Вы можете легко воспроизвести: объявить invarchar как varchar set invarchar = 'ta; aa; qq' SELECT Value from [dbo]. [SplitString] (invarchar, ';') SELECT Value from [dbo]. [SplitString] ('ta; aa; qq ','; ')
Патрик Дежарден
Мне нравится этот подход, но если количество возвращаемых объектов sys.all_objectsменьше, чем количество символов во входной строке, то строка будет усечена, и значения пропадут. Поскольку sys.all_objectsон используется просто как хитрость для генерации строк, есть лучшие способы сделать это, например, этот ответ .
Knuckles
55

Наконец, ожидание закончилось, в SQL Server 2016 они ввели функцию разделения строк:STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

Все другие методы разделения строки, такие как XML, таблица Tally, цикл while и т. Д., Были уничтожены этой STRING_SPLITфункцией.

Вот отличная статья со сравнением производительности: Сюрпризы производительности и предположения: STRING_SPLIT

P ரதீப்
источник
5
очевидно, отвечает на вопрос о том, как разделить строку для тех, у кого обновленные серверы, но те из нас, кто все еще застрял на 2008/2008 R2, должны будут пойти с одним из других ответов здесь.
mpag
2
Вам нужно посмотреть на уровень совместимости в вашей базе данных. Если оно меньше 130, вы не сможете использовать функцию STRING_SPLIT.
Луис Тейон,
На самом деле, если совместимость не 130 и вы используете 2016 (или Azure SQL), вы можете установить совместимость до 130, используя: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Michieal
22

Самый простой способ сделать это - использовать XMLформат.

1. Преобразование строки в строки без таблицы

ЗАПРОС

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

РЕЗУЛЬТАТ

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. Преобразование в строки из таблицы с идентификатором для каждой строки CSV.

ТАБЛИЦА ИСТОЧНИКОВ

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

ЗАПРОС

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

РЕЗУЛЬТАТ

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x
Сарат Аванаву
источник
Этот подход не работает, если @Stringсодержит запрещенные символы ... Я только что опубликовал ответ, чтобы решить эту проблему.
Shnugo 02
9

Мне нужен был быстрый способ избавиться +4от почтового индекса .

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

Нет процедуры ... нет UDF ... всего одна маленькая встроенная команда, которая делает то, что должна. Не модно, не элегантно.

При необходимости измените разделитель и т. Д., И он будет работать для чего угодно.

КорветГуру
источник
5
Вопрос не в этом. OP имеет значение типа «234,542,23», и они хотят разделить его на три строки ... 1-я строка: 234, 2-я строка: 542, 3-я строка: 23. Это сложная вещь, которую нужно сделать в SQL.
codeulike
7

если вы замените

WHILE CHARINDEX(',', @stringToSplit) > 0

с участием

WHILE LEN(@stringToSplit) > 0

вы можете удалить эту последнюю вставку после цикла while!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END
AviG
источник
Это приведет к усечению последнего символа последнего элемента. т.е. "AL, AL" станет "AL" | «A», то есть «ABC, ABC, ABC» станет «ABC» | «Азбука» | «AB»
разработчик Microsoft
добавление +1к, SELECT @pos = LEN(@stringToSplit)похоже, решает эту проблему. Однако функция SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)вернется, Invalid length parameter passed to the LEFT or SUBSTRING functionесли вы не добавите еще +1и третий параметр SUBSTRING. или вы можете заменить это назначение наSET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag
1
Я отправил некоторые улучшения (с бэк тестовых случаев) на мою страницу GitHub здесь . Я отправлю его в качестве ответа в этой ветке переполнения стека, когда у меня будет достаточно репутации, чтобы превысить пост «защита»
mpag
Я тоже обратил внимание на проблему, указанную Терри выше. Но данная логика @AviG настолько крута, что не дает сбоя в середине длинного списка токенов. Попробуйте этот тестовый вызов для проверки (этот вызов должен вернуть 969 токенов) select * from dbo.splitstring ('token1, token2 ,,,,,,,, token969') Затем я попробовал код, предоставленный mpag, чтобы проверить результаты для тех же позвоните выше и выяснили, что он может вернуть только 365 токенов. Наконец, я исправил код AviG выше и опубликовал функцию без ошибок в качестве нового ответа ниже, поскольку комментарий здесь допускает только ограниченный текст. Проверьте ответ от моего имени, чтобы попробовать.
Gemunu R Wickremasinghe
3

Все функции для разделения строк, использующие какой-либо цикл (итерации), имеют плохую производительность. Их следует заменить комплексным решением.

Этот код отлично работает.

CREATE FUNCTION dbo.SplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO
Игорь Мичев
источник
Этот подход не работает, если @Listсодержит запрещенные символы ... Я только что опубликовал ответ, чтобы решить эту проблему.
Shnugo 02
Я поддерживаю ваш ответ, потому что ваш работает с пробелом в качестве разделителя, а тот, который получил наибольшее количество голосов,
KMC
3

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

Хитрость заключается в том, чтобы сначала SELECT SomeString AS [*] FOR XML PATH('')правильно экранировать все запрещенные символы. Вот почему я заменяю разделитель на магическое значение, чтобы избежать проблем с ;разделителем as.

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

Результат

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F
Шнуго
источник
2

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

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END
pswg
источник
1

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

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit
Торстен Б. Хагеманн
источник
1

Это более узко. Когда я это делаю, у меня обычно есть список уникальных идентификаторов (INT или BIGINT), разделенных запятыми, которые я хочу преобразовать в таблицу для использования в качестве внутреннего соединения с другой таблицей, имеющей первичный ключ INT или BIGINT. Я хочу, чтобы возвращалась встроенная функция с табличным значением, чтобы у меня было наиболее эффективное соединение.

Пример использования:

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

Я позаимствовал эту идею из http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html , изменив ее на встроенное табличное значение и преобразовав в INT.

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO
Том Риган
источник
1

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

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs
Брайс
источник
если бы вы сделали это SET @StringToSeperate = @StringToSeperate+','непосредственно перед WHILEциклом, я думаю, вы могли бы удалить блок «добавить последнее значение». Смотрите также мой sol'n на github
mpag
На каком ответе это основано? Здесь много ответов, и это немного сбивает с толку. Спасибо.
jpaugh 01
1

Я немного изменил функцию + Andy Robinson. Теперь вы можете выбрать только нужную деталь из таблицы возврата:

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5

SNabi
источник
1

Если вам нужно быстрое специальное решение для общих случаев с минимумом кода, то этот рекурсивный двухстрочный алгоритм CTE сделает это:

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

Либо используйте это как отдельный оператор, либо просто добавьте указанные выше CTE к любому из ваших запросов, и вы сможете объединить полученную таблицу bс другими для использования в любых дальнейших выражениях.

редактировать (Шнуго)

Если вы добавите счетчик, вы получите индекс позиции вместе со списком:

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

Результат:

n   s
1   1
2   2333
3   344
4   4
алкоголь
источник
Мне нравится такой подход. Надеюсь, вы не возражаете, что я добавил некоторые улучшения прямо в ваш ответ. Просто не стесняйтесь редактировать это любым удобным способом ...
Шнуго
1

Я беру xml-маршрут, оборачивая значения в элементы (M, но все работает):

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)
Alex
источник
0

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


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

результат выглядит так

струна строкаb x y z

марджакония
источник
0

Лично я использую эту функцию:

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)
Кали
источник
0

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

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

Применение:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

Возможное использование (получить второе значение каждого разбиения):

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2
Гури Шанкар Эчур
источник
0

Рекурсивное решение на основе cte

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;
папараццо
источник
0

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

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END
Gemunu R Wickremasinghe
источник
0

Это основано на ответе Энди Робертсона, мне нужен был разделитель, отличный от запятой.

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

И использовать это:

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(Проверено на SQL Server 2008 R2)

РЕДАКТИРОВАТЬ: правильный тестовый код

Нунцио Токчи
источник
-1
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END
MiH
источник
-1

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

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO
Абхинав
источник
-1

Вот пример, который вы можете использовать как функцию или также можете поместить ту же логику в процедуру. - ВЫБОР * из [dbo] .fn_SplitString;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;
Сурья-АИС
источник
Этот подход не работает, если @vCSVсодержит запрещенные символы ... Я только что опубликовал ответ, чтобы решить эту проблему.
Shnugo 02
-1

/ *

Ответ на разделенную строку T-SQL
На основе ответов Энди Робинсона и AviG.
Расширенная функциональность ref: Функция LEN без конечных пробелов в SQL Server.
Этот «файл» должен быть действительным как файл уценки, так и файл SQL.


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

Тестовые случаи: см. URL-адрес, указанный выше как «расширенная функциональность».

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

* /

mpag
источник
откат в честь «Этот 'файл' должен быть действителен и как файл уценки, и как файл SQL»
mpag
-3

Самый простой способ:

  1. Установить SQL Server 2016
  2. Используйте STRING_SPLIT https://msdn.microsoft.com/en-us/library/mt684588.aspx

Работает даже в экспресс-редакции :).

Йован MSFT
источник
Не забудьте установить «Уровень совместимости» на SQL Server 2016 (130) - в студии управления щелкните правой кнопкой мыши базу данных, свойства / параметры / уровень совместимости.
Tomino
1
В исходном сообщении говорилось о SQL 2008 R2. Установка SQL 2016 может быть
Шон