为什么非贪心量词有时在Oracle正则表达式中不起作用?

时间:2013-05-22 22:30:39

标签: regex oracle oracle11g oracle10g regex-greedy

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支持部门的反应吗?
这个虫子已经知道了吗? 它有ID吗?

4 个答案:

答案 0 :(得分:24)

这是一个BUG!

你是对的,在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 with even more examples

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,

模式是一致的:第一次出现的贪婪用于第二次出现是否应该。

答案 1 :(得分:6)

观察反馈,我犹豫是否跳进去,但我走了; - )

根据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('1_@_2_a_3_@_4_a', '(\w)*?@(\w)*') from dual;

结果:1​​ _ @ _ 2_a_3 _

-- greedy followed by non-greedy
select regexp_substr('1_@_2_a_3_@_4_a', '(\w)*@(\w)*?') from dual;

结果:1​​ _ @

答案 2 :(得分:1)

你有一个非常好的赏金,所以我将尝试全面地解决这个问题。

您在正则表达式处理中假设不正确。

  1. Oracle与Perl正则表达式不兼容,它是 与POSIX兼容。 It describes its support for Perl as "Perl-Influenced"
  2. 使用Perl“*?”存在内在的语法冲突在Oracle中,如果你 阅读这个参考我的方式,Oracle合法地选择POSIX用法
  3. 你对perl如何处理“*?”的描述是不对的。
  4. 这是我们讨论过的选项的混搭。这个问题的关键在于案例30

        CASE    SRC                             TEXT               RE                FROM_WHOM                                          RESULT        
        ------- ------------------------------- ------------------ ----------------- -------------------------------------------------- --------------
              1 Egor's original source string   A=1,B=2,C=3,       .*B=.*?,          Egor's original pattern "doesn't work"             A=1,B=2,C=3,  
              2 Egor's original source string   A=1,B=2,C=3,       .*B=.?,           Egor's "works correctly"                           A=1,B=2,      
              3 Egor's original source string   A=1,B=2,C=3,       .*B=.+?,          Old Pro comment 1 form 2                           A=1,B=2,      
              4 Egor's original source string   A=1,B=2,C=3,       .+B=.*?,          Old Pro comment 1 form 1                           A=1,B=2,      
              5 Egor's original source string   A=1,B=2,C=3,       .*B=.{0,}?,       Old Pro comment 2                                  A=1,B=2,      
              6 Egor's original source string   A=1,B=2,C=3,       [^B]*B=[^Bx]*?,   Old Pro answer form 1 "good"                       A=1,B=2,      
              7 Egor's 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's original source string   A=1,B=2,C=3,       (.)*B=(.)*?,      TBone answer form 1                                A=1,B=2,      
              9 TBone answer example 2          1_@_2_a_3_@_4_a    (\w)*?@(\w)*      TBone answer example 2 form 1                      1_@_2_a_3_    
             10 TBone answer example 2          1_@_2_a_3_@_4_a    (\w)*@(\w)*?      TBone answer example 2 form 2                      1_@           
             30 Egor's original source string   A=1,B=2,C=3,       .*B=(.)*?,        Schemaczar Variant to force Perl operation         A=1,B=2,      
             31 Egor's 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's original source string   A=1,B=2,C=3,       .*B=.*{0,1}       Schemaczar Applying Egor's  'non-greedy'           A=1,B=2,C=3,  
             33 Egor's original source string   A=1,B=2,C=3,       .*B=(.)*{0,1}     Schemaczar Another variant of Egor's "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那样工作,也就是说,它不需要匹配模式来覆盖整个测试字符串。使用“^”开头和“$”结束标记也可以帮助您。

    我的剧本:

    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''s original pattern "doesn''t work"','Egor',1);
    Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=.?,','Egor''s "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''s  ''non-greedy''','Egor',32);
    Insert into TEST_PATS (RE,FROM_WHOM,PAT_GROUP,PAT_ORDER) values ('.*B=(.)*{0,1}','Schemaczar Another variant of Egor''s "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)*?@(\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''s original source string','Egor',1);
    Insert into TEST_STRINGS (TEXT,SRC,TEXT_GROUP,TEXT_ORDER) values ('1_@_2_a_3_@_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;
    

答案 3 :(得分:-1)

因为你选择了太多

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