Как написать RegEx для извлечения N-го значения из CSV-подобной строки используя regexp_substr()

Как можно расширить следующее регулярное выражение (в POSIX стандарте), таким образом чтобы в качестве разделителя строк могли выступать значения ,, ;, /, , т.е. комбинации запятой, точки с запятой, слеша, пробелов и символа табуляции.

Пример исходных данных:

ID  EMAIL
--  --------------------------------------------------
1   NULL
2   [email protected]
3   [email protected]; [email protected], [email protected]
4   [email protected], [email protected]
5   [email protected] [email protected]

В данном DB Fiddle я попытался извлечь второй email из каждой строки и хотел бы получить следующий результат (выходной датасет):

ID  EMAIL
--  --------------------------------------------------
1   NULL
2   NULL
3   [email protected]
4   [email protected]
5   [email protected]

Т.е. в результате не должны присутствовать символы разделители: ,, ;, /, Я попытался добавить POSIX значение [:space:] для того чтобы обрабатывать пробелы и знаки табуляции, но результат оказался неверным:

select
  id,
  regexp_substr (email, '(.*?)(([[:space:],;/])|($))', 1, 2, null, 1) as second_email
from tab;

ID  EMAIL
--  --------------------------------------------------
1   NULL
2   NULL
3   NULL
4   NULL
5   [email protected]

Подскажите как исправить POSIX RegEx.

NOTE: я работаю с Oracle 12.2, который не понимает RegEx символы такие как \s, поэтому приходится использовать только регулярки, которые поддерживаются POSIX стандартом.

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


Ответы (3 шт):

Автор решения: Akina
SELECT tab.id, jsontable.email_num, jsontable.email
FROM tab
CROSS JOIN JSON_TABLE('["' || REGEXP_REPLACE(tab.email, '[,;\/ ]+', '","') || '"]',
                      '$[*]' COLUMNS (email_num FOR ORDINALITY,
                                      email VARCHAR(255) PATH '$')) jsontable;

https://dbfiddle.uk/?rdbms=oracle_18&fiddle=182ab5331fc09c25ab534002c56c634f

→ Ссылка
Автор решения: 0xdb

Выглядит, что следующие регулярки работают:

select id, regexp_substr (
    email, '(\w.*?)(([[:space:],;/])|($))', 1, 2, null, 1) as second_email
from tab
/
select id, regexp_substr (
    email, '(.*?)(([[:space:],;/]+)|($))', 1, 2, null, 1) as second_email
from tab

Обе выводят:

        ID SECOND_EMAIL                                      
---------- --------------------------------------------------
         1                                                   
         2                                                   
         3 [email protected]                                  
         4 [email protected]                                  
→ Ссылка
Автор решения: Wiktor Stribiżew

Регулярное выражение (.*?)(([[:space:],;/])|($)) не всегда находит нужное совпадение, потому что (.*?) также находит пустую строку, а у вас в строках рядом с разделителями есть и пробельные символы, и [[:space:],;/] их и находит. Другими словами, вместо нужного значения (.*?) захватывает пустую строку, [[:space:],;/] находит пробел после, например, запятой, и возвращается совпадение.

Поэтому, вместо (.*?) нужно подставить такой шаблон, чтобы он не находил пустую строку, например

'([^[:space:]].*?)((' || '[[:space:],;/]' || ')|($))'

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

Шаблон ([^[:space:]].*?) не найдёт пустой строки, так как [^[:space:]] требует одного любого символа кроме пробельного, и именно с этого символа и начнётся совпадение.

→ Ссылка