Oracle SQL: возможно ли использовать SUBSTR и INSTR для возврата ВСЕХ строк в заданном текстовом поле? - PullRequest
2 голосов
/ 25 октября 2019

Я работаю с некоторыми данными, которые хранятся в столбце VARCHAR2 (4000), и данные в основном представляют собой текстовые комментарии, но также содержат даты в тексте. Я написал запрос, который использует SUBSTR и INSTR для сопоставления с образцом и нахождения ведущего текстового значения до даты, а затем SUBST для возврата значения даты, которое я затем преобразовываю в дату, используя TO_DATE. Это хорошо работает, однако у меня есть ряд записей, которые содержат несколько комментариев и, как таковые, несколько дат. Используя описанный выше метод, я могу указать только (n) -ное вхождение этого шаблона, есть ли способ вернуть ВСЕ даты при совпадении, а не только одно вхождение?

Вот примерданные в столбце varchar;

LOCKED ENTITY: ACCOUNT
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:47
CUST NOTES: <Please enter explanation here>
Customer notes are entered here.

UNLOCKED ENTITY: ACCOUNT
UNLOCKED BY USER: ops
UNLOCKED AT: 31/05/2004 11:49
UNLOCK NOTES: <Please enter explanation here>
Test

LOCKED ENTITY: USER
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:50
LOCK NOTES: <Please enter explanation here>
Test

UNLOCKED ENTITY: USER
UNLOCKED BY USER: ops
UNLOCKED AT: 24/08/2009 16:47
UNLOCKED NOTES: <Please enter explanation here>

Вот упрощенная версия запроса, который я использую (все остальные несоответствующие объединения и столбцы удалены для ясности);

select substr(VALUE, INSTR(VALUE,'LOCKED AT: ',1)+11, 10) as "DATE"
  from tableA a
  join tableB b
    on a.id = b.id
 where regexp_like (VALUE ,'ABC|DEF|GHI');  

DATE
----------
31/05/2004

Для вышеупомянутогоЗапрос Я хотел бы вернуть все даты, следующие за строкой 'LOCKED AT:' например;

 DATE
----------
31/05/2004
31/05/2004

Любая помощь будет принята с благодарностью. Для информации, версия БД - Oracle 10g, я попытался использовать REGEXP_COUNT, чтобы достичь чего-то, о чем я думал, но работает только с 11g.

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

Ответы [ 3 ]

3 голосов
/ 25 октября 2019

Вы можете использовать regexp_substr с connect by level для получения всех совпадений:

Предоставление данных образца:

WITH tableA(VALUE) AS (SELECT 'LOCKED ENTITY: ACCOUNT
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:47
CUST NOTES: <Please enter explanation here>
Customer notes are entered here.

UNLOCKED ENTITY: ACCOUNT
UNLOCKED BY USER: ops
UNLOCKED AT: 31/05/2004 11:49
UNLOCK NOTES: <Please enter explanation here>
Test

LOCKED ENTITY: USER
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:50
LOCK NOTES: <Please enter explanation here>
Test

UNLOCKED ENTITY: USER
UNLOCKED BY USER: ops
UNLOCKED AT: 24/08/2009 16:47
UNLOCKED NOTES: <Please enter explanation here>' FROM dual)

Запрос:

select ltrim( regexp_substr(VALUE,
                            '^LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,LEVEL,'m'), 
                            'LOCKED AT: ') as "Dates Locked" 
  FROM tableA
 CONNECT BY regexp_substr(VALUE,
                            '^LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,LEVEL,'m') IS NOT null

Результат:

Dates Locked
------------
31/05/2004
31/05/2004

Проблема с этим запросом заключается в том, что connect by выполняется для полной таблицы - чтобы избежать этого единственного возможного решения, это разделить эту часть и выполнить ее для любого datarow независимо:

SELECT ID, ltrim( regexp_substr(VALUE,
                            '^LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,lvl.column_value,'m'), 
                            'LOCKED AT: ') as "Dates Locked" 
  FROM tableA
  CROSS JOIN table(cast(multiset
    (select level from dual
    connect by regexp_substr(VALUE,
                            '^LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,LEVEL,'m') IS NOT null) 
    as sys.odcinumberlist)) lvl
2 голосов
/ 25 октября 2019

Вы можете использовать regexp_substr(), regexp_count(), regexp_like() и regexp_instr() функции регулярных выражений вместе с substr() функцией строкового оператора:

with tab as
(
select 
       regexp_substr(value,
              'LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,level)
              as Dates_Locked,
       regexp_instr(value,
              'LOCKED AT: ([[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}\.?)',1,level)      
              as Pos_Unlocked,
       value 
  from tableA a 
  join tableB b 
    on a.id = b.id
connect by level <= regexp_count(value,'LOCKED AT: ') 
)
select ltrim( Dates_Locked, 'LOCKED AT: ' ) as Dates_Locked
  from tab     
 where not regexp_like(substr(value,Pos_Unlocked-2,2),'UN','i');

 Dates Locked
 ------------
 31/05/2004
 31/05/2004

Demo

0 голосов
/ 25 октября 2019

Вы можете делать все это без регулярных выражений и просто используя простые строковые функции:

Установка Oracle :

CREATE TABLE test_data ( value ) AS
SELECT 'LOCKED ENTITY: ACCOUNT
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:47
CUST NOTES: <Please enter explanation here>
Customer notes are entered here.

UNLOCKED ENTITY: ACCOUNT
UNLOCKED BY USER: ops
UNLOCKED AT: 31/05/2004 11:49
UNLOCK NOTES: <Please enter explanation here>
Test

LOCKED ENTITY: USER
LOCKED BY USER: ops
LOCKED AT: 31/05/2004 11:50
LOCK NOTES: <Please enter explanation here>
Test

UNLOCKED ENTITY: USER
UNLOCKED BY USER: ops
UNLOCKED AT: 24/08/2009 16:47
UNLOCKED NOTES: <Please enter explanation here>' FROM DUAL;

Запрос :

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

WITH note_bounds ( value, start_pos, end_pos ) AS (
  SELECT REPLACE( value, CHR(13) ),
         1,
         INSTR( REPLACE( value, CHR(13) ), CHR(10) || CHR(10), 1 )
  FROM   test_data
UNION ALL
  SELECT value,
         end_pos + 2,
         INSTR( value, CHR(10) || CHR(10), end_pos + 2 )
  FROM   note_bounds
  WHERE  end_pos > 0
),
notes ( note ) AS (
  SELECT CASE end_pos
         WHEN 0
         THEN SUBSTR( value, start_pos )
         ELSE SUBSTR( value, start_pos, end_pos - start_pos )
         END
  FROM   note_bounds
),
note_data_bounds ( note, sep1, end1, sep2, end2, sep3, end3, sep4 ) AS (
  SELECT note,
         INSTR( note, ':', 1, 1 ),
         INSTR( note, CHR(10), 1, 1 ),
         INSTR( note, ':', 1, 2 ),
         INSTR( note, CHR(10), 1, 2 ),
         INSTR( note, ':', 1, 3 ),
         INSTR( note, CHR(10), 1, 3 ),
         INSTR( note, ':', 1, 5 )
  FROM   notes
)
SELECT SUBSTR( note, 1, sep1 - 8 ) AS action,
       SUBSTR( note, sep1 + 2, end1 - sep1 - 2 ) AS entity,
       SUBSTR( note, sep2 + 2, end2 - sep2 - 2 ) AS actor,
       TO_DATE( SUBSTR( note, sep3 + 2, end3 - sep3 - 2 ), 'DD/MM/YYYY HH24:MI' ) AS datetime,
       SUBSTR( note, sep4 + 2 ) AS notes
FROM   note_data_bounds;

Выход :

ACTION   | ENTITY  | ACTOR | DATETIME            | NOTES                                                              
:------- | :------ | :---- | :------------------ | :------------------------------------------------------------------
LOCKED   | ACCOUNT | ops   | 2004-05-31 11:47:00 | <Please enter explanation here><br>Customer notes are entered here.
UNLOCKED | ACCOUNT | ops   | 2004-05-31 11:49:00 | <Please enter explanation here><br>Test                            
LOCKED   | USER    | ops   | 2004-05-31 11:50:00 | <Please enter explanation here><br>Test                            
UNLOCKED | USER    | ops   | 2009-08-24 16:47:00 | <Please enter explanation here>                                    

db <> скрипка здесь

...