Как разделить разделительную строку на SQL Server без создания функции?

Я работаю с базой данных SQL Server. У меня есть столбец, который содержит список с разделителями, и мне нужно написать запрос, который разбивает значения списка на строки. Из просмотра StackOverflow и остальной части Интернета я знаю, что это обычная проблема. Фактически, я нашел здесь обширный анализ:

http://www.sommarskog.se/arrays-in-sql.html

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

Без CREATE я знаю, что могу использовать функцию PARSENAME, что-то вроде этого (спасибо Nathan Bedford at Как разбить строку, чтобы я мог получить доступ к элементу x?):.

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

Однако PARSENAME работает только для списков из 4-х элементов или меньше. Поэтому мой вопрос заключается в следующем: как написать запрос для разделения строки с разделителями более чем на 4 элемента без создания новых объектов в базе данных?

EDIT:

Спасибо всем за быстрые ответы. Возможно, я оставил некую важную информацию - я взаимодействую с базой данных через ODBC-соединение. В дополнение к операторам CREATE, похоже, есть другие утверждения, которые не работают. Например, я не могу использовать DECLARE в одном утверждении для определения переменной, которая будет использоваться в другом выражении. Насколько я могу судить, я должен положить все в один оператор SELECT (хотя WITH также, похоже, работает для объявления общих таблиц). К сожалению, все предлагаемые до сих пор решения, похоже, требуют объявления переменных вне инструкции SELECT, и это не работает. Пожалуйста, несите меня - я учусь, когда иду.

Ответы

Ответ 1

Версия с использованием XML.

declare @S varchar(100) = 'Hello John Smith'

select 
  n.r.value('.', 'varchar(50)')
from (select cast('<r>'+replace(@S, ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
  cross apply s.XMLCol.nodes('r') as n(r)

Вместо использования таблицы Замените @T тем, что вы используете в таблице.

-- Test table
declare @T table (ID int, Col varchar(100))
insert into @T values (1, 'Hello John Smith')
insert into @T values (2, 'xxx yyy zzz')

select 
  T.ID,
  n.r.value('.', 'varchar(50)')
from @T as T
  cross apply (select cast('<r>'+replace(replace(Col,'&','&amp;'), ' ', '</r><r>')+'</r>' as xml)) as S(XMLCol)
  cross apply S.XMLCol.nodes('r') as n(r)

Разделение строки 'Hello John Smith' без использования переменной

select 
  n.r.value('.', 'varchar(50)')
from (select cast('<r>'+replace('Hello John Smith', ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
  cross apply s.XMLCol.nodes('r') as n(r)

Ответ 2

пример, используя таблицу встроенных мастеров..spt_values ​​

DECLARE @String VARCHAR(1000)
    SELECT @String ='1,4,77,88,4546,234,2,3,54,87,9,6,4,36,6,9,9,6,4,4,68,9,0,5'

    SELECT SUBSTRING(',' + @String + ',', Number + 1,
    CHARINDEX(',', ',' + @String + ',', Number + 1) - Number -1)AS VALUE
    FROM master..spt_values
    WHERE Type = 'P'
    AND Number <= LEN(',' + @String + ',') - 1
    AND SUBSTRING(',' + @String + ',', Number, 1) = ','
    GO

Подробнее см. здесь: Разделить строку с помощью таблицы номеров

Ответ 3

Вы можете использовать рекурсивный CTE для постепенного извлечения одного элемента

Пример таблицы

create table aTable(a int identity primary key, b int, c varchar(100))
insert aTable values (1, 'this is a test string')
insert aTable values (1, 'this is another test string')
insert aTable values (2, 'here is a test string to put the others to shame')
insert aTable values (4, '')
insert aTable values (5, null)
insert aTable values (5, '-the end- ')

Запрос

;with tmp(a, b, c, position, single) as (
select a, b,
    STUFF(c, 1, CHARINDEX(' ', c + ' .'), ''),
    1,
    convert(nvarchar(max),left(c, CHARINDEX(' ', c + ' .') -1))
from aTable
union all
select a, b,
    STUFF(c, 1, CHARINDEX(' ', c + ' .'), ''),
    position+1,
    convert(nvarchar(max),left(c, CHARINDEX(' ', c + ' .') -1))
from tmp
where c > '')
select a, b, single, position
from tmp
order by a, position

Примечания:

  • Разделитель здесь - это одно пространство, которое ищет CHARINDEX. Точка в ' .' требуется, потому что SQL Server обычно не считает конечные пробелы значимыми.
  • ВСЕ столбцы исходной таблицы могут быть сохранены в CTE, просто добавьте их. Здесь я показываю пример из 2 столбцов a и b, сохраненных в результате, причем столбец c разбивается на single и дополнительный столбец для указания позиции.

Ответ 4

Для поздних участников этого вопроса статья http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings обеспечивает отличный анализ производительности для различных опций. Некоторые из рассмотренных вариантов включают (скопированные с сайта для справки):

CLR

.Net код http://sqlblog.com/blogs/adam_machanic/archive/2009/04/28/sqlclr-string-splitting-part-2-even-faster-even-more-scalable.aspx.

CREATE ASSEMBLY CLRUtilities FROM 'c:\DLLs\CLRUtilities.dll' 
  WITH PERMISSION_SET = SAFE;
GO

CREATE FUNCTION dbo.SplitStrings_CLR
(
   @List      NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE ( Item NVARCHAR(4000) )
EXTERNAL NAME CLRUtilities.UserDefinedFunctions.SplitString_Multi;
GO

XML

CREATE FUNCTION dbo.SplitStrings_XML
(
   @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

CTE

CREATE FUNCTION dbo.SplitStrings_CTE
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS @Items TABLE (Item NVARCHAR(4000))
WITH SCHEMABINDING
AS
BEGIN
   DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter);

   WITH a AS
   (
       SELECT
           [start] = 1,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll),
           [value] = SUBSTRING(@List, 1, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll) - 1)
       UNION ALL
       SELECT
           [start] = CONVERT(INT, [end]) + @ld,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll),
           [value] = SUBSTRING(@List, [end] + @ld, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll)-[end][email protected])
       FROM a
       WHERE [end] < @ll
   )
   INSERT @Items SELECT [value]
   FROM a
   WHERE LEN([value]) > 0
   OPTION (MAXRECURSION 0);

   RETURN;
END
GO

Функция

CREATE FUNCTION dbo.SplitStrings_Moden
(
   @List NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
  WITH E1(N)        AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                         UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                         UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
       E2(N)        AS (SELECT 1 FROM E1 a, E1 b),
       E4(N)        AS (SELECT 1 FROM E2 a, E2 b),
       E42(N)       AS (SELECT 1 FROM E4 a, E2 b),
       cteTally(N)  AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
  SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
    FROM cteStart s;

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

Ответ 5

Для sql Server >= 2016 вы можете использовать string_split, как показано ниже:

SELECT * FROM string_split('Hello John Smith', ' ')

Выход

+-------+
| value |
+-------+
| Hello |
| John  |
| Smith |
+-------+

Ответ 7

Я бы просто взял одну из многих функций, которые создают таблицу, и вместо того, чтобы вернуть значение, поместите ее в переменную таблицы. Затем используйте переменную таблицы. Вот один пример, который возвращает таблицу.

http://www.codeproject.com/KB/database/SQL_UDF_to_Parse_a_String.aspx

Ответ 8

использование UDF делает его наиболее гибким для всех проектов, которое я обычно использую в своем блоге,

http://sqlthis.blogspot.com/2005/02/list-to-table.html

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

он работает, и, надеюсь, он будет работать и для вас.

отредактируйте 2017-08-09, как было предложено, я клонировал блок кода ниже. спасибо!

CREATE FUNCTION udfListToTable (@HList VarChar(2000), @Delimiter CHAR(1))
RETURNS @ListTable TABLE (Field1 VARCHAR(6))
  AS
  BEGIN
  --By: Francisco Tapia
  --Date: 2/1/2005
  --Purpose: To convert a Comma delimited text to a Temp Variable table To help avoid dynamic sql
  -- Instead you can join the temp table or use it in your where clause if a field is IN the subquery
       DECLARE @FieldText as VarChar(6)

       IF RIGHT(RTRIM(@HLIST),1) <>@Delimiter
       SET @HList = @HList + @Delimiter

       WHILE CHARINDEX(@Delimiter, @HList) > 0
       BEGIN
            IF CHARINDEX(@Delimiter, @HList) > 0
            BEGIN
                 SELECT @FieldText =LEFT(@HList, CHARINDEX(@Delimiter, @HList)-1)
            END
       ELSE 
       BEGIN
            SELECT @FieldText = RTRIM(LTRIM(@HList))
       END
       --Insert into Variable Table
       INSERT INTO @ListTable(Field1)
       SELECT RTRIM(LTRIM(@FieldText))
       --Remove Item from list
       SELECT @HList = RIGHT(RTRIM(@HList), LEN(RTRIM(@HList)) -
            CHARINDEX(@Delimiter, @HList))
       END 
       RETURN
  END

Ответ 9

 DECLARE @cols  AS NVARCHAR(max), 
        @Val   VARCHAR(100)='Hi- Hello break this-Wall', 
        @Deli  VARCHAR(50)='-', 
        @query AS NVARCHAR(max) 

SELECT @cols = Stuff((SELECT ',' + Quotename(id) 
                      FROM   (SELECT stringpieceid AS ID, 
                                     stringpiece 
                              FROM 
                     [Utility].[dbo].[Splitstringtotable](@Val, @Deli)) 
                             X 
                      FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, 
               '') 

SELECT @query = 
'SELECT * FROM (SELECT StringPieceID as ID,StringPiece from [Utility].[dbo].[SplitStringToTable](''' 
         + @Val + ''',''' + @Deli + '''))X PIVOT  (     MAX(StringPiece)     for [ID] in (' + @cols 
         + ') ) P' 

PRINT @query 

EXEC Sp_executesql 
  @query