функция dbo.fn_split, отсекающая следующий символ после разделяемого символа / делиметра

#sql-server

#sql-сервер

Вопрос:

Я запустил этот запрос :

 select * From dbo.fn_split('Age,15,14,193,188 ',',')
  

Он возвращает значения, но отсекает один символ перед каждым значением

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

 select * From dbo.fn_split('Age, 15, 14, 193, 188 ',',')
  

И это сработало. Но я хочу знать, почему не работает с запятыми

 select * From dbo.fn_split ('Age,15,14,193,188 ',',')
  

Комментарии:

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

2. Я не верю, что существует стандартная функция Microsoft SQL Server с именем «fn_split», скорее всего, это определяемая пользователем функция. Если вы можете опубликовать код функции, который поможет любому диагностировать проблему. В качестве альтернативы, если вы используете SQL Server 2016 или выше, используйте встроенную функцию «string_split», например ‘select * from string_split(‘1,2,3,4,5,6′,’,’)’

3. Кто бы не удивился, если функция OP ( fn_split ) использует CURSOR WHILE цикл or? 🙂

4. Я на самом деле проголосовал за то, чтобы закрыть это как не по теме. Однако OP ответил на этот вопрос, а не с ответом, который кому-либо поможет. Требуется DDL fn_split , чтобы сделать ответ полезным (хороший разделитель просто вернул бы конечное значение as '188 ' , а не удалял первый символ каждого значения). Это говорит о том, что сама функция имеет недостатки; что невозможно устранить без DDL.

5. Я бы согласился, @LuisCazares, но изображение OP (в его ответе) дает порядковую позицию; что невозможно с разделителем XML. Возможно, это разделитель JSON, но я в этом сомневаюсь. Позор, поскольку они (более чем вероятно) быстро узнают, что итеративный разделитель будет медленным, быстрым.

Ответ №1:

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

 CREATE FUNCTION [dbo].[DelimitedSplit8K_LEAD]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table” produces values from 0 up to 10,000...
     -- enough to cover VARCHAR(8000)
 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
                ),                          --10E 1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E 2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E 4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT 0 UNION ALL
                 SELECT TOP (DATALENGTH(ISNULL(@pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N 1 (starting position of each "element" just once for each delimiter)
                 SELECT t.N 1
                   FROM cteTally t
                  WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
        Item = SUBSTRING(@pString,s.N1,ISNULL(NULLIF((LEAD(s.N1,1,1) OVER (ORDER BY s.N1) - 1),0)-s.N1,8000))
   FROM cteStart s
;
GO
  

Ответ №2:

ИЗМЕНИТЬ ФУНКЦИЮ [dbo] .[fn_Split](@text varchar(8000), @delimiter varchar(20) = ‘,’)

ВОЗВРАЩАЕТ ТАБЛИЦУ @Strings

(

первичный КЛЮЧ ИДЕНТИФИКАТОРА position int,

значение varchar(8000)

)

КАК

НАЧАТЬ

ОБЪЯВИТЬ @index int

SET @index = -1

WHILE (LEN(@text) > 0)

НАЧАТЬ

 SET @index = CHARINDEX(@delimiter , @text) 

IF (@index = 0) AND (LEN(@text) > 0) 

  BEGIN  

    INSERT INTO @Strings VALUES (@text)

      BREAK 

  END 

IF (@index > 1) 

  BEGIN  

    INSERT INTO @Strings VALUES (LEFT(@text, @index - 1))  

    SET @text = RIGHT(@text, (LEN(@text) - @index)) 

  END 

ELSE

  SET @text = RIGHT(@text, (LEN(@text) - @index))

END
  

Возврат

ЗАВЕРШЕНИЕ

Комментарии:

1. Найдено быстрое исправление внутри функции в основном LTRIM(RTRIM(@text)) перед началом обработки @text

Ответ №3:

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

Select * From dbo.fn_split(dbo.fn_split('Age,15,14,193,188 ',',')) Для Select * From dbo.fn_split('Age,15,14,193,188',',') . Другими словами, избавившись от лишнего пробела после числа 188, вы получите желаемый результат.

Решение

Комментарии:

1. Без DDL fn_split это на самом деле никому не помогает. И «Решение» — это набор результатов, а не исправленный SQL.