Предложения Oracle CLOB REGEXP_REPLACE - PullRequest
0 голосов
/ 12 января 2019

У меня есть текст CLOB, похожий на:

A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838

Я хочу, чтобы мой результат был похож на

A:123, 983, 122, B:232, 392, C:921, D:221, 121, 838

Обратите внимание,

  • Это огромные данные и определенно более 4000 символов.
  • Символы могут повторяться.
  • Номера всегда уникальны. Также может идти до 11 цифр.
  • Исходные данные таблицы не должны быть изменены.
  • Результат не должен быть отсортирован

Это выглядит простой проблемой, когда нам просто нужно удалить дубликаты в тексте CLOB. Я не могу разработать логику в SQL, кто-нибудь может предложить?

Обновление: Я нашел решение с помощью пользовательской программы Java, где я повторяю и удаляю дубликаты. Он отлично работает для меня. Все еще любопытно увидеть подход SQL.

Ответы [ 2 ]

0 голосов
/ 13 января 2019

Этот запрос анализирует строку (в поисках двоеточий) и возвращает позицию второго вхождения строки для буквы aech, предшествующей двоеточию:

with col as 
(select 'A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838' col from dual),
t1 as(
select col, instr(col,':',1,level)-1 pos
from col
connect by level <= length(col) - length(replace(col,':',null))
),
t2 as (
select to_char(substr(col,pos,2)) str,
pos
from t1),
t3 as (
select 
 str, pos,
 row_number() over (partition by str order by pos) rn
from t2)
select
  str, pos
 from t3
 where rn = 2
;

По сути, вы разделяете строку для каждого двоеточия (я использую подход length(replace для повышения производительности по сравнению с регулярным выражением), извлекая подстроку X: и ее положение.

Чем использовать row_number() для получения второго вхождения partition by str.

Обратите внимание, что он работает с CLOB любой длины, потому что только строка длиной 2 преобразуется в VARCHAR.

Результат

STR             POS
-------- ----------
A:                8 
B:               29 
D:               50

Интерпретация

заменить A: двумя пробелами в CLOB, начиная с позиции 8.

заменить B: двумя пробелами в CLOB, начиная с позиции 29.

и т.д.

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

Таким образом, основная идея состоит в том, чтобы сохранить первые символы pos-1 одинаковыми, а replace остальную часть строки и, наконец, CONCAT ввести обе части:

 concat(substr(txt,1, pos-1) , replace( substr(txt, pos), str, '  '));

Я реализовал весь логик в функции, которая возвращает измененный CLOB, так что это может быть используется как в запросах, так и в выражении update:

select id, col, upd_clob(col) from tc;

update tc
set col = upd_clob(col);

Код функции

create or replace function upd_clob(txt CLOB) return CLOB
as
v_txt CLOB := txt;
begin
     for r_upd in ( 
        with dt as 
         (select txt from dual),
        t1 as(
         select txt, instr(txt,':',1,level)-1 pos
         from dt
         connect by level <= length(txt) - length(replace(txt,':',null))
         ),
        t2 as (
         select to_char(substr(txt,pos,2)) str,
         pos
         from t1),
        t3 as (
         select 
          str, pos,
          row_number() over (partition by str order by pos) rn
         from t2)
         select
           str, pos
         from t3
         where rn = 2)
     loop
        v_txt := concat(substr(v_txt,1, r_upd.pos-1) , replace( substr(v_txt, r_upd.pos), r_upd.str, '  '));
     end loop;
     return(v_txt);
end;
/

Вы можете посчитать производительность для большого CLOB не очень хорошей (хотя IMO намного лучше, чем альтернативная реализация REGEXP). Возможным подходом к настройке будет дополнительный логик в функции, которая распознает короткие строки и обрабатывает их как строки VARCHAR.

0 голосов
/ 12 января 2019

Вы можете использовать regexp_substr с instr и regexp_count в качестве дополнительного участника:

select listagg(letter||':'||nr,',') within group (order by letter)
       as "Result String"
  from
  (
  select letter, listagg(nr,',') within group (order by letter) as nr
    from
    (
    with t(str) as
    (
     select 'A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838' from dual
    )
    select substr(regexp_substr(str,'[^:]+',instr(str,':'),level),1, instr(str,',')
                                                                    -instr(str,':')-1) nr,     
           substr(str,instr(str,':',1,level)-1,1) letter      
      from t
    connect by level < regexp_count(str,'[^:]+')
    ) 
   group by letter  
   );

Result String
-------------------------------------------
A:122,123,983,B:232,392,C:921,D:121,221,838

P.S. вам нужно преобразование to_char для столбца clob (а именно col_clob). Заменить

select 'A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838' from dual

с

select to_char(col_clob) from your_table

Rextester Demo

...