Ответов (25)25
Вы можете найти решение в разделе Пользовательская функция SQL для анализа строки с разделителями» (из проекта Code ).
Вы можете использовать эту простую логику:
Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null
WHILE LEN(@products) > 0
BEGIN
IF PATINDEX('%|%', @products) > 0
BEGIN
SET @individual = SUBSTRING(@products,
0,
PATINDEX('%|%', @products))
SELECT @individual
SET @products = SUBSTRING(@products,
LEN(@individual + '|') + 1,
LEN(@products))
END
ELSE
BEGIN
SET @individual = @products
SET @products = NULL
SELECT @individual
END
END
Сначала создайте функцию (используя 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
Использование остается прежним.
Вы можете использовать числовую таблицу для синтаксического анализа строк.
Создайте таблицу физических номеров:
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]
Производительность здесь не впечатляет, но вызов функции с таблицей из миллиона строк - не лучшая идея. При выполнении разбиения строки на несколько строк я бы избегал этой функции.
По-моему, вы, ребята, слишком усложняете задачу. Просто создайте 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()));
}
};
А как насчет использования 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
Я использую ответ Фредерика, но это не сработало в 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
Рекурсивное решение 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 |
Хотя это похоже на ответ на основе 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','')
Здесь я публикую простой способ решения
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',' ')
если кто-то хочет получить только одну часть отложенного текста, может использовать это
выберите * 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
)
В большинстве решений здесь используются циклы 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 не масштабируются, а также о лучших альтернативах при разделении строк, поступающих с уровня приложения:
- Разделите струны правильным способом - или лучшим способом
- Разделение строк: продолжение
- Разделение строк: теперь с меньшим количеством T-SQL
- Сравнение методов разделения / конкатенации строк
- Обработка списка целых чисел: мой подход
- Разделение списка целых чисел: еще один обзор
- Подробнее о разделении списков: настраиваемые разделители, предотвращение дублирования и поддержание порядка
- Удаление дубликатов из строк в SQL Server
Однако в SQL Server 2016 или более поздней версии вам следует посмотреть STRING_SPLIT()
и STRING_AGG()
:
опираясь на решение @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
Вот функция, которая выполнит задачу по разделению строки и доступу к элементу 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
ПРОСТОЕ РЕШЕНИЕ ДЛЯ РАЗБОРКИ ПЕРВОГО И ПОСЛЕДНЕГО ИМЕНИ
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')
)
Я оставил это,
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 всегда должен быть там.
Этот вопрос не о подходе к разделению строк , а о том, как получить n-й элемент .
Все ответы здесь делают какое - то строка расщепления с помощью рекурсии, CTE
с, множественные 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+, есть JSON_VALUE
и OPENJSON
.
С помощью JSON_VALUE
мы можем передать позицию как индексный массив.
Для документации четко сказано: OPENJSON
Когда OPENJSON анализирует массив JSON, функция возвращает индексы элементов в тексте JSON в виде ключей.
Строка , как 1,2,3
нужно ничего более скобки: [1,2,3]
.
Строка слов вроде " this is an example
должно быть" ["this","is","an","example"]
.
Это очень простые строковые операции. Просто попробуйте:
DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;
--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));
- См. Это для позиционно-безопасного разделителя строк (с нуля ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
В этом посте я протестировал различные подходы и обнаружил, что OPENJSON
это действительно быстро. Даже намного быстрее, чем знаменитый метод delimitedSplit8k () ...
ОБНОВЛЕНИЕ 2 - получить значения, безопасные для типов
Мы можем использовать массив внутри массива, просто используя doubled [[]]
. Это позволяет использовать типизированное 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
CREATE TABLE test(
id int,
adress varchar(100)
);
INSERT INTO test VALUES(1, 'Ludovic Aubert, 42 rue de la Victoire, 75009, Paris, France'),(2, 'Jose Garcia, 1 Calle de la Victoria, 56500 Barcelona, Espana');
SELECT id, value, COUNT(*) OVER (PARTITION BY id) AS n, ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT NULL)) AS rn, adress
FROM test
CROSS APPLY STRING_SPLIT(adress, ',')
Я не верю, что в SQL Server есть встроенная функция разделения, поэтому, кроме UDF, единственный известный мне ответ - захватить функцию PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME берет строку и разбивает ее на символ точки. Он принимает число в качестве второго аргумента, и это число указывает, какой сегмент строки следует вернуть (работая от конца к началу).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Очевидная проблема - когда строка уже содержит точку. Я все еще думаю, что использование UDF - лучший способ ... есть другие предложения?
Вот 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
You would call it like this:
Select * From SplitString('Hello John Smith',' ')
Изменить: обновленное решение для обработки разделителей с len> 1, как в:
select * From SplitString('Hello**John**Smith','**')
Современный подход с использованием 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-й элемент из номера строки.
Ответ Аарона Бертрана великолепен, но ошибочен. Он не точно обрабатывает пробел в качестве разделителя (как это было в примере в исходном вопросе), поскольку функция длины удаляет конечные пробелы.
Ниже приведен его код с небольшой поправкой, позволяющей использовать разделитель пробелов:
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
);