Oracle SQL - Редактирование нескольких вхождений всех, кроме последних четырех цифр чисел различной длины в текстовом повествовании.

Есть ли простой способ, возможно, с использованием REGEXP_REPLACE или т.п., чтобы отредактировать все, кроме последних четырех цифр чисел (или различной длины от 5 или выше), появляющихся в свободном тексте (в тексте может быть несколько вхождений отдельных чисел)?

E.g.

Input = 'This is a test text with numbers 12345, 9876543210 and separately number 1234567887654321 all buried within the text'

Output = 'This is a test text with numbers ****5, *****3210 and separately number ************4321 all buried within the text'

С REGEX_REPLACE, очевидно, просто заменить все числа на *, но меня раздражает сохранение последних четырех цифр и замена их правильным количеством *.

Любая помощь приветствуется!

(Просто для контекста, из-за обычных бизнес-ограничений это должно было быть сделано в запросе, извлекающем данные, а не с использованием реальных функций редактирования СУБД Oracle).

Большое спасибо.


person Lorielus    schedule 31.10.2019    source источник


Ответы (2)


Вы можете попробовать следующее регулярное выражение:

regexp_replace(txt, '(\d{4})(\d+(\D|$))', '****\2')

Это захватывает последовательности из 4 цифр, за которыми следует хотя бы одна цифра, затем нецифровой символ (или конец строки) и заменяет их 4 звездочками.

Демонстрация скрипта БД:

with t as (select 'select This is a test text with numbers 12345, 9876543210 and separately number 1234567887654321 all buried within the text' txt from dual)
select regexp_replace(txt, '(\d{4})(\d+\D)', '****\2') new_text from t
| NEW_TEXT                                                                                                                    |
| :-------------------------------------------------------------------------------------------------------------------------- |
| select This is a test text with numbers ****5, ****543210 and separately number ****567887654321 all buried within the text |

Изменить

Вот упрощенная версия, предложенная Алексеем в комментариях:

regexp_replace(txt, '(\d{4})(\d+)', '****\2')

Это работает из-за жадности механизма регулярных выражений, который будет поглощать как можно больше '\d+'.

person GMB    schedule 31.10.2019
comment
Почему часть \D? Это может быть проблемой, если строка заканчивается цифрой, я прав? - person Aleksej; 31.10.2019
comment
\D — нечисловой символ. Он определяет конец числа. - person GMB; 31.10.2019
comment
@Алексей: хорошая мысль. Я изменил регулярное выражение, чтобы правильно управлять числами в конце строки. Посмотрите эту демонстрацию. - person GMB; 31.10.2019
comment
Это работает хорошо, но я считаю, что вы могли бы использовать даже '(\d{4})(\d+)'; я что-то пропустил? - person Aleksej; 31.10.2019
comment
@Aleksej: Я думаю, что ты прав. Я отредактировал свой ответ с помощью этого более простого решения. Спасибо! - person GMB; 31.10.2019
comment
Большое спасибо - это выглядит великолепно - хотя это будет работать только с точки зрения замены правильным количеством * s, где можно добавить кластеры из четырех. Есть ли способ настроить его так, чтобы вместо этого он искал одну цифру, за которой следуют как минимум четыре цифры, и заменял каждую такую ​​цифру на *? - person Lorielus; 31.10.2019

Если вам действительно нужно сохранить длину чисел, то (я думаю) это невозможно сделать за один шаг. Вам нужно будет разбить строку на числа, а не числа, а затем заменить цифры отдельно:

SELECT listagg(CASE WHEN REGEXP_LIKE(txt, '\d{5,}') -- if the string is of your desired format
                    THEN LPAD('*', LENGTH(txt) - 4,'*') || SUBSTR(txt, LENGTH(txt) -3) -- replace all digits but the last 4 with *
                    ELSE txt END)
       within GROUP (ORDER BY lvl)
  FROM (SELECT LEVEL lvl, REGEXP_SUBSTR(txt, '(\d+|\D+)', 1, LEVEL ) txt -- Split the string in numerical and non numerical parts 
          FROM (select 'This is a test text with numbers 12345, 9876543210 and separately number 1234567887654321 all buried within the text' AS  txt FROM dual)
       CONNECT BY REGEXP_SUBSTR(txt, '(\d+|\D+)', 1, LEVEL ) IS NOT NULL)

Результат:

This is a test text with numbers *2345, ******3210 and separately number ************4321 all buried within the text

И поскольку ваш пример заменил первые цифры вашего первого номера, вы также можете заменить как минимум 4 цифры:

SELECT listagg(CASE WHEN REGEXP_LIKE(txt, '\d{5,}') -- if the string is of your desired format
                    THEN LPAD('*', GREATEST(LENGTH(txt) - 4, 4),'*') || SUBSTR(txt, GREATEST(LENGTH(txt) -3, 5)) -- replace all digits but the last 4 with *
                    ELSE txt END)
       within GROUP (ORDER BY lvl)
  FROM (SELECT LEVEL lvl, REGEXP_SUBSTR(txt, '(\d+|\D+)', 1, LEVEL ) txt -- Split the string in numerical and non numerical parts 
          FROM (select 'This is a test text with numbers 12345, 9876543210 and separately number 1234567887654321 all buried within the text' AS  txt FROM dual)
       CONNECT BY REGEXP_SUBSTR(txt, '(\d+|\D+)', 1, LEVEL ) IS NOT NULL)

(Добавлено GREATEST во второй строке, чтобы заменить как минимум 4 цифры.)

Результат:

This is a test text with numbers ****5, ******3210 and separately number ************4321 all buried within the text
person Radagast81    schedule 31.10.2019
comment
Это решение дает точный ожидаемый результат, поэтому +1. - person GMB; 31.10.2019
comment
Спасибо @GMB, лично я бы остался с вашим решением и всегда печатал фиксированную длину звездочек. Длина чисел - это дополнительная информация, которую я не хочу давать в ситуациях, когда я скрываю фактические данные... - person Radagast81; 31.10.2019