T- SQL, разрезать строку на куски по 35 символов без нарезки слов - PullRequest
2 голосов
/ 24 января 2020

ОК, я везде искал правильный синтаксис. Скажем, у меня есть веревка неизвестной длины, например: «Быстрая коричневая лиса перепрыгивает через ленивую собаку. Быстрая коричневая лиса прыгает через ленивую собаку. Быстрая коричневая лиса прыгает через ленивую собаку. Быстрая коричневая лиса прыгает через ленивую собаку. Быстрая коричневая лиса перепрыгивает через ленивую собаку. '

Мне нужно разбить ее на куски по 35 символов каждый: «Быстрая коричневая лиса перепрыгивает через пробел» - это разделитель.

правила таковы:

1 - каждый фрагмент длиной не более 35 символов

2 - НЕ разбивать слова.

2.1 - если общая длина больше чем 35, go назад, пока не найдете первое пространство, где длина меньше 35 и вырезать его там.

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

, то есть если строка делится на 5 35 блоков символов, одна запись возвращает любые избыточные разливы в большее количество строк в наборе 5

________________________________________________________________________________________________________________________________________________________________________________|
|level  |   Val1                            |Val2                           |   Val3                        |   Val4                            |   Val5                        |
________________________________________________________________________________________________________________________________________________________________________________|
|   1   |The quick brown fox jumps over the | lazy dog. The quick brown fox | jumps over the lazy dog. The  | quick brown fox jumps over the    | lazy dog. The quick brown fox |
|   2   | jumps over the lazy dog. The      | quick brown fox jumps over the| lazy dog.                     |NULL                               |   NULL                        |       
________________________________________________________________________________________________________________________________________________________________________________|

Я нашел здесь некоторый код такая работа, но я не могу получить результат, ограниченный 35 кусками символов

, что он делает: получить количество разделителей в строке (количество пробелов), чем разделить все, используя CTE, в таблицу. чем объединить все обратно. но в "Splitvalues" cte, если я разбиваю mainLevel на чанки по 5, он работает, но объединяется не по длине, а по группе из 5, и я все еще не понимаю, как развернуть результат в столбец 6, как описано.

DECLARE     @ColumnLen             INT           = 35
       ,@BNotAllowNullinValue1 BIT           = 1
       ,@Delim                 VARCHAR(5)    = SPACE(1)
       ,@DelimCount            INT
       ,@OriginalStr           NVARCHAR(MAX) = 'The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.'
       ,@ReturnColumnCount     INT           = 5

SET @OriginalStr = @OriginalStr + @Delim
SET @DelimCount = ((LEN(@OriginalStr + '|')-1) - (LEN(REPLACE(@OriginalStr + '|',@Delim,''))-1)) / LEN(LTRIM(RTRIM(@Delim)) + '|')

---- test data
;WITH Splitvalues(SplitValue ,MainLevel ,ProcessLevel,LastPos,Original)
AS (SELECT TOP 1 LTRIM(RTRIM(SUBSTRING(@OriginalStr,1,ABS(CHARINDEX(@Delim,@OriginalStr,1)))))
                ,1 as MainLevel
                ,1 as ProcessLevel
                ,CHARINDEX(@Delim,@OriginalStr,1 + 1) AS LastPos
                ,@OriginalStr
    UNION ALL
    SELECT LTRIM(RTRIM(SUBSTRING(@OriginalStr,LastPos + 1,ABS((CHARINDEX(@Delim,@OriginalStr,LastPos + 1) - LastPos)))))
          ,CASE (ProcessLevel % 5) WHEN  0 THEN MainLevel +1 ELSE MainLevel END  as MainLevel
          ,ProcessLevel + 1 as ProcessLevel
         ,CHARINDEX(@Delim,@OriginalStr,LastPos + 1) AS LastPos
          ,@OriginalStr
    FROM Splitvalues
    WHERE ProcessLevel <= @DelimCount
          AND ISNULL(LTRIM(RTRIM(SUBSTRING(@OriginalStr,LastPos + 1,ABS((CHARINDEX(@Delim,@OriginalStr,LastPos + 1) - LastPos))))),'') <> '')
---- actual query;
,cte(MainLevel,ProcessLevel,combined,rn)
     AS (SELECT MainLevel,ProcessLevel,Splitvalue ,rn = ROW_NUMBER() OVER(PARTITION BY MainLevel ORDER BY MainLevel,ProcessLevel)FROM Splitvalues)
,cte2(MainLevel,ProcessLevel ,finalstatus ,rn)
     AS (SELECT MainLevel,cte.ProcessLevel ,CONVERT(VARCHAR(MAX),combined)  ,1 FROM cte WHERE rn = 1
         UNION ALL
         SELECT cte2.MainLevel,cte2.ProcessLevel  +1 ,CONVERT(VARCHAR(MAX),cte2.finalstatus + @Delim + cte.combined+ @Delim )
               ,cte2.rn + 1
         FROM cte2
         INNER JOIN cte ON cte.MainLevel = cte2.MainLevel  AND cte.rn = cte2.rn + 1
        )
     SELECT MainLevel,MAX(finalstatus),LEN(MAX(finalstatus)+'|')
     FROM cte2
     GROUP BY MainLevel

спасибо за помощь.

Ответы [ 2 ]

3 голосов
/ 27 января 2020

Если ваша версия MS Sql Server - 2017 или более поздняя, ​​вы можете использовать для этого STRING_SPLIT & STRING_AGG.

Пример:

declare @OriginalStr nvarchar(max);
set @OriginalStr = N'The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog';

declare @ColumnLen int = 35;

declare @parts table 
(
  part_id int identity(1,1) primary key,
  part_content nvarchar(max)
);

declare @lines table 
(
  line_id int primary key,
  line_content nvarchar(max)
);

-- splitting the string on the spaces
insert into @parts (part_content)
select value
from string_split(@OriginalStr, ' ') spl

-- glueing the parts back together
insert into @lines (line_id, line_content)
select 
 lineNr,
 string_agg(part_content, ' ') as line
from
(
  select part_content
  , floor(1.0*(sum(len(part_content)+1) 
               over (order by part_id))/(@ColumnLen-1))+1 as lineNr
  from @parts 
) q
group by lineNr;

-- pivoting the lines
select
ceiling((line_id-0.1)/5) as [Level],
max(case when line_id%5 = 1 then line_content end) as Val1,
max(case when line_id%5 = 2 then line_content end) as Val2,
max(case when line_id%5 = 3 then line_content end) as Val3,
max(case when line_id%5 = 4 then line_content end) as Val4,
max(case when line_id%5 = 0 then line_content end) as Val5
from @lines l
group by ceiling((line_id-0.1)/5)
order by [Level];

GO
Level | Val1                              | Val2                              | Val3                               | Val4                               | Val5                          
:---- | :-------------------------------- | :-------------------------------- | :--------------------------------- | :--------------------------------- | :-----------------------------
1     | The quick brown fox jumps over    | the lazy dog. The quick brown fox | jumps over the lazy dog. The quick | brown fox jumps over the lazy dog. | The quick brown fox jumps over
2     | the lazy dog. The quick brown fox | jumps over the lazy dog           | <em>null</em>                               | <em>null</em>                               | <em>null</em>                          

db <> fiddle здесь

В более старой версии Sql Server это должно работать для заполнения переменной таблицы для линий.

with rcte as
(
   select 
   1 as lineNr,
   1 as strPos,
   @ColumnLen + 1 - cast(
     charindex(N' ',
       reverse(
         substring(@OriginalStr, 1, @ColumnLen)
       ) 
     ) as int) as lineLen

   union all

   select
   lineNr + 1,
   strPos + lineLen,
   @ColumnLen + 1 - cast(
     charindex(N' ',
       reverse(
         substring(@OriginalStr, strPos+lineLen, @ColumnLen)
       )
     ) as int)
   from rcte
   where strPos+lineLen < len(@OriginalStr)
)
insert into @lines (line_id, line_content)
select 
 lineNr,
 line = rtrim(substring(@OriginalStr, pos, lineLen))
from rcte;
1 голос
/ 27 января 2020

Чтобы найти место для вырезания, я бы предложил получить первые 35 символов строки, а затем найти последний пробел. Это можно сделать, используя reverse и charindex:

Позиция крайнего правого пространства:

36-CHARINDEX(' ', REVERSE(LEFT(@txt, 36)))

Теперь я бы использовал рекурсивный CTE. Каждый уровень будет обрезать следующий бит строки до тех пор, пока он не станет пустым.

Я включил приведенный ниже запрос, в котором я закрепляю CTE с помощью искателя размещения сверху, а в рекурсивной части я снова используйте искатель мест.

Рекурсия заканчивается, когда нет оставшегося символа.

Наконец, чтобы установить его в 5 столбцах подряд, я пронумеровал каждый случай 0 ... n

Затем я использую тот факт, что модуль n% 5 находится в (0,1,2,3,4), и что n / 5 в качестве целочисленного деления дает номер строки, в которую должен быть выведен столбец.

declare @txt varchar(max)=N'The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.'
;
WITH cte
AS
(SELECT
        TRIM(LEFT(@txt, n)) grp
       ,0 grpn
       ,TRIM(SUBSTRING(@txt, n + 1, LEN(@txt))) remainder
    FROM (SELECT
            36 - CHARINDEX(' ', REVERSE(LEFT(@txt, 36))) n) a
    UNION ALL
    SELECT
        TRIM(LEFT(remainder, (n))) grp
       ,grpn + 1
       ,TRIM(SUBSTRING(remainder, (n) + 1, LEN(remainder))) remainder
    FROM cte
    OUTER APPLY (SELECT
            36 - CHARINDEX(' ', REVERSE(LEFT(remainder, 36))) n) a
    WHERE LEN(remainder) > 0)
SELECT
    max(iif(grpn%5=0,grp,null)) Val1
    ,max(iif(grpn%5=1,grp,null)) Val2
    ,max(iif(grpn%5=2,grp,null)) Val3
    ,max(iif(grpn%5=3,grp,null)) Val4
    ,max(iif(grpn%5=4,grp,null)) Val5
FROM cte
group by grpn/5
...