Подтвердить что ты не робот

Почему не-жадный квантификатор иногда не работает в 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 по этой проблеме?
Известна ли ошибка?
Имеет ли он идентификатор?

4b9b3361

Ответ 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