Почему не-жадный квантификатор иногда не работает в Oracle regex?
IMO, этот запрос должен возвращать A=1,B=2,
SELECT regexp_substr('A=1,B=2,C=3,', '.*B=.*?,') as A_and_B FROM dual
Но вместо этого возвращает целую строку A=1,B=2,C=3,
. Зачем?
UPD: Oracle 10.2+ требуется для использования метасимволов в стиле Perl в регулярных выражениях.
UPD2:
Более ясная форма моего вопроса (чтобы избежать вопросов о версии Oracle и доступности расширения регулярного выражения в стиле Perl):
Почему в той же системе ненасытный квантификатор иногда работает так, как ожидалось, а иногда нет?
Это работает правильно:
regexp_substr('A=1,B=2,C=3,', 'B=.*?,')
Это не работает:
regexp_substr('A=1,B=2,C=3,', '.*B=.*?,')
fiddle
UPD3:
Да, это, кажется, ошибка.
Может ли кто-нибудь обеспечить реакцию поддержки Oracle по этой проблеме?
Известна ли ошибка?
Имеет ли он идентификатор?
Ответы
Ответ 1
Это ошибка!
Вы правы, что в Perl 'A=1,B=2,C=3,' =~ /.*B=.*?,/; print $&
печатает A=1,B=2,
То, что вы наткнулись, - это ошибка, которая все еще существует в Oracle Database 11g R2. Если один и тот же оператор регулярных выражений (исключая модификатор жадности) дважды появляется в регулярном выражении, оба появления будут иметь жадность, обозначенную первым появлением, независимо от жадности, указанной вторым. То, что это ошибка, наглядно демонстрируется этими результатами:
SQL> SELECT regexp_substr('A=1,B=2,C=3,', '[^B]*B=[^Bx]*?,') as good FROM dual;
GOOD
--------
A=1,B=2,
SQL> SELECT regexp_substr('A=1,B=2,C=3,', '[^B]*B=[^B]*?,') as bad FROM dual;
BAD
-----------
A=1,B=2,C=3,
Единственное различие между двумя регулярными выражениями заключается в том, что "хороший" исключает "x" как возможное совпадение во втором списке соответствия. Так как "x" не отображается в целевой строке, исключая его, это не имеет никакого значения, но, как вы можете видеть, удаление "x" имеет большое значение. Это должно быть ошибкой.
Вот еще примеры из Oracle 11.2: (SQL Fiddle с еще большим количеством примеров)
SELECT regexp_substr('A=1,B=2,C=3,', '.*B=.*?,') FROM dual; => A=1,B=2,C=3,
SELECT regexp_substr('A=1,B=2,C=3,', '.*B=.*,') FROM dual; => A=1,B=2,C=3,
SELECT regexp_substr('A=1,B=2,C=3,', '.*?B=.*?,') FROM dual; => A=1,B=2,
SELECT regexp_substr('A=1,B=2,C=3,', '.*?B=.*,') FROM dual; => A=1,B=2,
-- Changing second operator from * to +
SELECT regexp_substr('A=1,B=2,C=3,', '.*B=.+?,') FROM dual; => A=1,B=2,
SELECT regexp_substr('A=1,B=2,C=3,', '.*B=.+,') FROM dual; => A=1,B=2,C=3,
SELECT regexp_substr('A=1,B=2,C=3,', '.+B=.+,') FROM dual; => A=1,B=2,C=3,
SELECT regexp_substr('A=1,B=2,C=3,', '.+?B=.+,') FROM dual; => A=1,B=2,
Шаблон согласован: жадность первого вхождения используется для второго вхождения, должно ли оно быть или нет.
Ответ 2
Глядя на обратную связь, я смущаюсь вскакивать, но здесь я иду; -)
В соответствии с Oracle docs, *? и +? сопоставить "предыдущее подвыражение". Для *? а именно:
Соответствует нулю или более вхождениям предыдущего подвыражения(nongreedyFootref 1). Соответствует пустой строке, когда это возможно.
Чтобы создать группу подвыражений, используйте скобки():
Рассматривает выражение в круглых скобках как единое целое. Выражение может быть строкой или сложным выражением, содержащим операторы.
Вы можете обратиться к подвыражению в обратной ссылке.
Это позволит вам использовать жадные и не жадные (на самом деле много разных времен) в том же регулярном выражении с ожидаемыми результатами. Для вашего примера:
select regexp_substr('A=1,B=2,C=3,', '(.)*B=(.)*?,') from dual;
Чтобы сделать точку более ясной (надеюсь), этот пример использует жадные и не жадные в том же regexp_substr, с разными (правильными) результатами в зависимости от того, где? (он НЕ использует правило только для первого подвыражения, которое он видит). Также обратите внимание, что подвыражение (\ w) будет соответствовать только буквенно-цифровым символам и подчеркиванию, а не @.
-- non-greedy followed by greedy
select regexp_substr('[email protected][email protected]_4_a', '(\w)*[email protected](\w)*') from dual;
результат: 1 _ @_ 2_a_3 _
-- greedy followed by non-greedy
select regexp_substr('[email protected][email protected]_4_a', '(\w)*@(\w)*?') from dual;
результат: 1 _ @
Ответ 3
У вас действительно отличная щедрость, поэтому я попытаюсь приглушить ее всесторонне.
Вы делаете неверные допущения в своей регулярной обработке выражений.
- Oracle НЕ совместим с регулярными выражениями Perl, это
совместим с POSIX. Он описывает свою поддержку Perl как
"Perl-Influenced"
- Существует внутренний конфликт синтаксиса вокруг использования Perl "*?" в Oracle, если вы
прочитайте эту ссылку так, как я это делаю, и Oracle законно выбирает использование POSIX
- Ваше описание того, как Perl обрабатывает "*?" не совсем верно.
Вот мэшап опций, которые мы обсуждали. Ключом к этой проблеме является случай 30
CASE SRC TEXT RE FROM_WHOM RESULT
------- ------------------------------- ------------------ ----------------- -------------------------------------------------- --------------
1 Egor original source string A=1,B=2,C=3, .*B=.*?, Egor original pattern "doesn't work" A=1,B=2,C=3,
2 Egor original source string A=1,B=2,C=3, .*B=.?, Egor "works correctly" A=1,B=2,
3 Egor original source string A=1,B=2,C=3, .*B=.+?, Old Pro comment 1 form 2 A=1,B=2,
4 Egor original source string A=1,B=2,C=3, .+B=.*?, Old Pro comment 1 form 1 A=1,B=2,
5 Egor original source string A=1,B=2,C=3, .*B=.{0,}?, Old Pro comment 2 A=1,B=2,
6 Egor original source string A=1,B=2,C=3, [^B]*B=[^Bx]*?, Old Pro answer form 1 "good" A=1,B=2,
7 Egor original source string A=1,B=2,C=3, [^B]*B=[^B]*?, Old Pro answer form 2 "bad" A=1,B=2,C=3,
8 Egor original source string A=1,B=2,C=3, (.)*B=(.)*?, TBone answer form 1 A=1,B=2,
9 TBone answer example 2 [email protected][email protected]_4_a (\w)*[email protected](\w)* TBone answer example 2 form 1 [email protected]_2_a_3_
10 TBone answer example 2 [email protected][email protected]_4_a (\w)*@(\w)*? TBone answer example 2 form 2 [email protected]
30 Egor original source string A=1,B=2,C=3, .*B=(.)*?, Schemaczar Variant to force Perl operation A=1,B=2,
31 Egor original source string A=1,B=2,C=3, .*B=(.*)?, Schemaczar Variant of Egor to force POSIX A=1,B=2,C=3,
32 Egor original source string A=1,B=2,C=3, .*B=.*{0,1} Schemaczar Applying Egor 'non-greedy' A=1,B=2,C=3,
33 Egor original source string A=1,B=2,C=3, .*B=(.)*{0,1} Schemaczar Another variant of Egor "non-greedy" A=1,B=2,C=3,
Я уверен, что CASE 30 - это то, что, как вы думали, вы пишете, - то есть вы подумали, что "*?" имел более сильную ассоциацию, чем сам "*". Правда, для Perl, я думаю, но для Oracle (и предположительно канонического POSIX) RE, "*?" имеет более низкий приоритет и ассоциативность, чем "*". Итак, Oracle читает это как "(. *)?" (случай 31), тогда как Perl читает его как "(.) *?", то есть случай 30.
Заметки 32 и 33 указывают, что "* {0,1}" не работает как "*?" .
Обратите внимание, что Oracle REGEXP не работает как LIKE, то есть не требует, чтобы шаблон соответствия охватывал всю тестовую строку. Использование маркеров "^" и "$" могут помочь вам в этом.
Ответ 4
Мой script:
SET SERVEROUTPUT ON
<<DISCREET_DROP>> begin
DBMS_OUTPUT.ENABLE;
for dropit in (select 'DROP TABLE ' || TABLE_NAME || ' CASCADE CONSTRAINTS' AS SYNT
FROM TABS WHERE TABLE_NAME IN ('TEST_PATS', 'TEST_STRINGS')
)
LOOP
DBMS_OUTPUT.PUT_LINE('Dropping via ' || dropit.synt);
execute immediate dropit.synt;
END LOOP;
END DISCREET_DROP;
/
--------------------------------------------------------
-- DDL for Table TEST_PATS
--------------------------------------------------------
CREATE TABLE TEST_PATS
( RE VARCHAR2(2000),
FROM_WHOM VARCHAR2(50),
PAT_GROUP VARCHAR2(50),
PAT_ORDER NUMBER(9,0)
) ;
/
--------------------------------------------------------
-- DDL for Table TEST_STRINGS
--------------------------------------------------------
CREATE TABLE TEST_STRINGS
( TEXT VARCHAR2(2000),
SRC VARCHAR2(200),
TEXT_GROUP VARCHAR2(50),
TEXT_ORDER NUMBER(9,0)
) ;
/
--------------------------------------------------------
-- DDL for View REGEXP_TESTER_V
--------------------------------------------------------
CREATE OR REPLACE FORCE VIEW REGEXP_TESTER_V (CASE_NUMBER, SRC, TEXT, RE, FROM_WHOM, RESULT) AS
select pat_order as case_number,
src, text, re, from_whom,
regexp_substr (text, re) as result
from test_pats full outer join test_strings on (text_group = pat_group)
order by pat_order, text_order;
/
REM INSERTING into TEST_PATS
SET DEFINE OFF;
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.*?,','Egor' original pattern "doesn''t work"','Egor',1);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.?,','Egor' "works correctly"','Egor',2);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=(.)*?,','Schemaczar Variant to force Perl operation','Egor',30);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=(.*)?,','Schemaczar Variant of Egor to force POSIX','Egor',31);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.*{0,1}','Schemaczar Applying Egor' ''non-greedy''','Egor',32);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=(.)*{0,1}','Schemaczar Another variant of Egor' "non-greedy"','Egor',33);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('[^B]*B=[^Bx]*?,','Old Pro answer form 1 "good"','Egor',6);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('[^B]*B=[^B]*?,','Old Pro answer form 2 "bad"','Egor',7);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.+?,','Old Pro comment 1 form 2','Egor',3);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.{0,}?,','Old Pro comment 2','Egor',5);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.+B=.*?,','Old Pro comment 1 form 1','Egor',4);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('(.)*B=(.)*?,','TBone answer form 1','Egor',8);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('(\w)*[email protected](\w)*','TBone answer example 2 form 1','TBone',9);
Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('(\w)*@(\w)*?','TBone answer example 2 form 2','TBone',10);
REM INSERTING into TEST_STRINGS
SET DEFINE OFF;
Insert into TEST_STRINGS (TEXT,SRC,TEXT_GROUP,TEXT_ORDER) values ('A=1,B=2,C=3,','Egor' original source string','Egor',1);
Insert into TEST_STRINGS (TEXT,SRC,TEXT_GROUP,TEXT_ORDER) values ('[email protected][email protected]_4_a','TBone answer example 2','TBone',2);
COLUMN SRC FORMAT A50 WORD_WRAP
COLUMN TEXT FORMAT A50 WORD_WRAP
COLUMN RE FORMAT A50 WORD_WRAP
COLUMN FROM_WHOM FORMAT A50 WORD_WRAP
COLUMN RESULT FORMAT A50 WORD_WRAP
SELECT * FROM REGEXP_TESTER_V;
Ответ 5
потому что вы слишком много выбираете
SELECT
regexp_substr(
'A=1,B=2,C=3,',
'.*?B=.*?,'
) as A_and_B, -- Now works as expected
regexp_substr(
'A=1,B=2,C=3,',
'B=.*?,'
) as B_only -- works just fine
FROM dual
Сценарий SQL: http://www.sqlfiddle.com/#!4/d41d8/11450