Разбиение одной строки на несколько строк путем разбиения текста после фиксированной длины в oracle

#sql #oracle #split

#sql #Oracle #разделение

Вопрос:

У меня есть таблица, которая содержит данные примерно такого вида:

 CREATE TABLE UDA_DATA 
( uda VARCHAR2(20), 
value_text VARCHAR2(4000) 
); 

Insert into UDA_DATA values('Material_ID','PBL000129 PBL000132 PBL000130 PBL000131 PBL000133'); 
Insert into UDA_DATA values('Material_ID','PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371'); 
   commit;
  

Теперь, если мы выберем данные из этой таблицы, это даст результат примерно такого:

 select * from UDA_DATA;
  

Это дает результат примерно такого:

введите описание изображения здесь

Но, однако, я ожидаю чего-то подобного:

введите описание изображения здесь

Означает, что он должен разбить value_text на две или более строк, если длина символа превышает 30. Кроме того, столбец uda должен иметь суффикс 1,2 ..n и он не должен разбивать текст между ними.

Написал рекуррентный CTE для получения результата:

 with rcte (rn, uda, value, chunk_num, value_text) as (
  select rownum,
    uda,
    substr(value_text, 1, 30),
    1,
    substr(value_text, 31)
  from uda_data
  union all
  select rn,
    uda,
    substr(value_text, 1, 30),
    chunk_num   1,
    substr(value_text, 31)
  from rcte
  where value_text is not null
)
select uda || chunk_num as uda, value
from rcte
order by rn, chunk_num;
  

Который дает результат, подобный этому:

введите описание изображения здесь

В третьей строке это разбиение текста между ними, что неверно, хотите получить результат чего-то такого:

введите описание изображения здесь

Любая помощь будет высоко оценена.

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

1. regexp_replace(value_text,'(.{1,30} )s','1,') заменяет пробелы запятыми в необходимых местах

Ответ №1:

Вот один из способов:

  • Разделите строки на слова
  • Рекомбинируйте слова длиной до 30 символов

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

Я использовал match_recogonize для группировки строк длиной до тридцати символов. Затем listagg рекомбинировать слова в каждой группе.

Вы могли бы адаптировать recursive with для этого тоже

Что дает:

 with ranks as (
  select u.*,
         row_number () over (
           order by uda, value_text
         ) rk
  from   uda_data u
), rws as (
  select rk, uda, rn,
         regexp_substr ( value_text, '[^ ] ', 1, rn ) || ' ' str
  from   ranks, lateral (
    select level rn from dual
    connect by level <= regexp_count ( value_text, ' ' )   1
  ) 
), grps as (
  select *
  from   rws
    match_recognize (
      partition by rk
      order by rn
      measures
        match_number() as grp,
        sum ( length ( str ) ) as len
      all rows per match 
      pattern ( thirty  )
      define 
        thirty as sum ( length ( str ) ) <= 30
    )
)
  select uda || grp, 
         listagg ( str ) 
           within group ( order by rn ) strs 
  from   grps
  group by rk, uda || grp;
  
UDA||GRP        STRS                             
Material_ID1    PBL000129 PBL000132 PBL000130     
Material_ID2    PBL000131 PBL000133               
Material_ID1    PBL0001341 PBL0001381             
Material_ID2    PBL0001351 PBL0001361             
Material_ID3    PBL0001371 
  

Примечание: трюк с разделением-рекомбинацией увеличивает количество обрабатываемых строк. Если входные строки будут длинными, этот метод может быть очень медленным. Если большинство разделится на 2-3 группы, это вероятно нормально — хотя, очевидно, протестируйте на своих данных!

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

1. Привет, Крис, большое тебе спасибо за твое время и помощь. Ваше решение работает для меня.

Ответ №2:

Проблема с вашим запросом заключается в том, что, хотя вы хотите разбить строку на пробелы, вы даже не пытаетесь это сделать. Вместо этого вы используете substr(value_text, 1, 30) , который не заботится о том, где расположены пробелы в строке.

Вы можете использовать INSTR для определения позиции разделения. Приведенный ниже запрос работает нормально, пока внутри нет кода длиной более 30 символов (в этом случае INSTR возвращается -1, что приводит к циклу в рекурсивном запросе). Возможно, вы захотите скорректировать запрос для этого случая.

 with rcte (rn, uda, value, chunk_num, value_text) as (
  select rownum,
    uda,
    substr(trim(value_text), 1, instr(substr(value_text || ' ', 1, 31), ' ', -1) - 1),
    1,
    trim(substr(trim(value_text), instr(substr(value_text || ' ', 1, 31), ' ', -1)   1))
  from uda_data
  union all
  select rn,
    uda,
    substr(value_text, 1, instr(substr(value_text || ' ', 1, 31), ' ', -1) - 1),
    chunk_num   1,
    trim(substr(value_text, instr(substr(value_text || ' ', 1, 31), ' ', -1)   1))
  from rcte
  where value_text is not null
)
select uda || rn || '/' || chunk_num as uda, value
from rcte
order by rn, chunk_num;
  

Демонстрация:https://dbfiddle.uk/?rdbms=oracle_18amp;fiddle=8dfc8e55a12c4666b4bc7bfcaceea2d2

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

1. Привет, Торстен, большое тебе спасибо за твое время и помощь. Ваше решение работает для меня.

Ответ №3:

  1. замените пробел запятой на наибольшую ширину <=30:
 select
  u.*
 ,regexp_replace(value_text,'(.{1,30} )s','1,') modified
from uda_data u;
  

Результат:

 UDA                  VALUE_TEXT                                                   MODIFIED
-------------------- ------------------------------------------------------------ --------------------------------------------------------------------------------
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133            PBL000129 PBL000132 PBL000130 PBL000131,PBL000133
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371       PBL0001341 PBL0001381 PBL0001351 PBL0001361,PBL0001371
  
  1. Разделять запятыми:
 select *
from uda_data u, 
     xmltable(
         'ora:tokenize(concat(",",.),",")[position()>1]'
         passing regexp_replace(value_text,'(.{1,30} )s','1,')
         columns 
            n for ordinality,
            v varchar2(100) path '.'
         );
  

Результат:

 UDA                  VALUE_TEXT                                                            N V
-------------------- ------------------------------------------------------------ ---------- ---------------------------------------------
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133                     1 PBL000129 PBL000132 PBL000130 PBL000131
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133                     2 PBL000133
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371                1 PBL0001341 PBL0001381 PBL0001351 PBL0001361
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371                2 PBL0001371

  

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

1. Классное решение. Было бы здорово, если бы у Oracle была regexp_split_to_table функция, подобная PostgreSQL. Поскольку этого пока не происходит, здорово, что это можно обойти, как показано вами.

2. @ThorstenKettner в Oracle существует множество различных решений для разделения строк. В нем есть даже внутренние функции, такие как dbms_utility.comma_to_table или xmltable / xmlquery (tokenize) и т.д., Но часто для этого лучше создать собственную конвейерную функцию