为什么在Oracle正则表达式中,非贪婪量词有时无法起作用?

29

我认为,这个查询应该返回 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,,为什么?


更新1:

需要使用Oracle 10.2+才能在正则表达式中使用Perl风格的元字符。

更新2:

更明确地表达我的问题(避免有关Oracle版本和Perl风格正则表达式扩展可用性的问题):

在同一系统上,为什么非贪婪量词有时按预期工作,有时不起作用?

以下内容可以正确运行:

regexp_substr('A=1,B=2,C=3,', 'B=.*?,')

这个不起作用:

regexp_substr('A=1,B=2,C=3,', '.*B=.*?,')

Fiddle

更新3:

是的,这似乎是一个bug。

Oracle对此问题有何反应?

这个bug已经被发现了吗?它有一个ID吗?


是的,就像这样:(https://dev59.com/zGQn5IYBdhLWcg3wmILY#16756711)。可以使用 '.+B=.*?,' 或者 '.*B=.+?,' 代替。 - Old Pro
甚至更好的是,使用.*B=.{0,}?, - Old Pro
@AndrewWolfe - 我不需要什么特别的。在Oracle中,?的含义与Perl RE中相同 :-) - Egor Skriptunoff
@EgorSkriptunoff - Perl中的“*?”有一个在POSIX中含义不明确的含义。 - Andrew Wolfe
1
Perl的复合运算符"*?"是两个有效的单字符运算符的序列。这些单字符运算符在Perl "*?"所在的语法位置上也是语法上有效的。因此,解析器会做出判断。一个好的语言规范应该指定优先级或结合性,以确定哪个是哪个。然而,POSIX表示它是未定义的(告诉我们使用括号),Perl手册也没有承诺太多。 - Andrew Wolfe
显示剩余4条评论
4个回答

24

这是个BUG!

您是对的,在Perl中,'A=1,B=2,C=3,' =~ /.*B=.*?,/; print $&会输出A=1,B=2,

你发现的是Oracle Database 11g R2仍存在的一个bug。如果一个正则表达式中出现了两次完全相同的正则表达式原子(包括量词但不包括贪婪修饰符),则无论第二个出现中指定了多么的贪婪程度,它们都将具有由第一个出现确定的贪婪性。这明显是一个bug,以下结果清楚地表明了这一点(这里,“完全相同的正则表达式原子”是[^B]*):

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'确实产生了很大的差异。这一定是一个bug。

以下是来自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,

模式是一致的:无论是否应该这样做,第一次出现的贪婪性都用于第二次出现。


1
嗯,是的,这个假设似乎是正确的:http://www.sqlfiddle.com/#!4/d41d8/11473 - Ben
1
是的,我也认为这是一个错误。你能提供官方确认吗? - Egor Skriptunoff
奇怪的是,通过将?交换到第一个通配符位置并将交换到后面,可以获得在Q中期望的结果:regexp_substr('A=1,B=2,C=3,', '.?B=.,')。这符合“第一次贪婪匹配”的假设。 - Glen Best
@OldPro - 感谢您理解我的问题的本质。实际上,您的答案对我来说并没有什么新意。我认为需要权威参考才能获得悬赏。 - Egor Skriptunoff
在第一段中,您能否添加确切的正则表达式运算符是什么(例如)? - Peter Mortensen
记录一下,在Oracle 19中仍然存在一个bug。 - René Nyffenegger

7

看到反馈,我有些犹豫,但还是决定回答 ;-)

根据 Oracle文档,*? 和 +? 匹配“前面的子表达式”。对于 *?,具体如下:

匹配 0 次或多次前面的子表达式(非贪婪模式Footref 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_@


0

你的赏金很高,所以我会全面地试图解决它。

你在正则表达式处理中做出了错误的假设。

  1. Oracle不兼容Perl正则表达式,而是与POSIX兼容。 它将其对Perl的支持描述为“受Perl影响”
  2. 在Oracle中使用Perl的"*?"存在内在的语法冲突,如果您像我一样阅读该参考文献,Oracle合法地选择了POSIX用法
  3. 您对Perl如何处理“*?”的描述并不完全正确。

这是我们讨论过的选项的混搭。 该问题的关键在于第30个案例。

    情况     源                                文本               结果                来自谁                                         
    ------- ------------------------------- ------------------ ----------------- -------------------------------------------------- --------------
          1 Egor的原始源字符串            A=1,B=2,C=3,       .*B=.*?,          Egor的原始模式“不起作用”             A=1,B=2,C=3,  
          2 Egor的原始源字符串            A=1,B=2,C=3,       .*B=.?,           Egor的“工作正常”                           A=1,B=2,      
          3 Egor的原始源字符串            A=1,B=2,C=3,       .*B=.+?,          Old Pro评论1形式2                           A=1,B=2,      
          4 Egor的原始源字符串            A=1,B=2,C=3,       .+B=.*?,          Old Pro评论1形式1                           A=1,B=2,      
          5 Egor的原始源字符串            A=1,B=2,C=3,       .*B=.{0,}?,       Old Pro评论2                                  A=1,B=2,      
          6 Egor的原始源字符串            A=1,B=2,C=3,       [^B]*B=[^Bx]*?,   Old Pro答案形式1“好”                       A=1,B=2,      
          7 Egor的原始源字符串            A=1,B=2,C=3,       [^B]*B=[^B]*?,    Old Pro答案形式2“坏”                        A=1,B=2,C=3,  
          8 Egor的原始源字符串            A=1,B=2,C=3,       (.)*B=(.)*?,      TBone答案形式1                                A=1,B=2,      
          9 TBone答案示例2               1_@_2_a_3_@_4_a    (\w)*?@(\w)*      TBone答案示例2形式1                      1_@_2_a_3_    
         10 TBone答案示例2               1_@_2_a_3_@_4_a    (\w)*@(\w)*?      TBone答案示例2形式2                      1_@           
         30 Egor的原始源字符串            A=1,B=2,C=3,       .*B=(.)*?,        Schemaczar变体以强制Perl操作         A=1,B=2,      
         31 Egor的原始源字符串            A=1,B=2,C=3,       .*B=(.*)?,        Schemaczar的Egor变体以强制POSIX      A=1,B=2,C=3,  
         32 Egor的原始源字符串            A=1,B=2,C=3,       .*B=.*{0,1}       Schemaczar应用Egor的“非贪婪”           A=1,B=2,C=3,  
         33 Egor的原始源字符串            A=1,B=2,C=3,       .*B=(.)*{0,1}     Schemaczar的另一种Egor“非贪婪”变体  A=1,B=2,C=3,  

我非常确定CASE 30就是你想要写的-也就是说,你认为"*?"比"*"本身有更强的关联性。我猜Perl是这样的,但对于Oracle(以及可能是规范的POSIX)RE,"*?"的优先级和结合性低于"*"。因此,Oracle将其读作"(.*)?"(case 31),而Perl将其读作"(.)*?",也就是case 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;

嘿 @EgorSkriptunoff,那个悬赏奖励怎么样了...拜托了? - Andrew Wolfe
POSIX规范:“多个相邻的重复符号('+'、'*'、'?'和间隔)的行为会产生未定义的结果。”请参见http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html#tag_09_04第9.4.6小节。(需要免费注册。) - Andrew Wolfe
1
Andrew,我不知道你是如何阅读Oracle的文档并得出它合法地采用了POSIX的使用方式。显然量词的优先级是?与其后紧密绑定以使前面的量词变为非贪婪。你的回答中没有任何内容能够解释老程序员(即我)的示例行为如何与Oracle暗示的任何规则集一致。你还错过了我的最爱:.*?B=.*,生成A=1,B=2,,而这绝对不可能正确。 - Old Pro
@OldPro,抱歉我错过了你的情况。我不同意Oracle使绑定优先级清晰明了的说法。你认为“?”的绑定更紧密是相当合理的,但我没有看到Oracle像你所说的那样在文档中说明它。可以说这是我太温顺了,但当一个正则表达式不能给我想要的结果时,我就开始添加括号,直到正则表达式引擎和我达成一致的结果。我认为在Oracle中,“*?”更一致地对括号内的前一个子表达式进行“非贪婪”操作,而不是在括号外的子表达式上进行操作。同样,“?”在任何子表达式后面都是合法的,所以我将“*?”视为模棱两可并加上括号。 - Andrew Wolfe
1
看起来 @EgorSkriptunoff 不太可能因为我们的麻烦而奖励我们任何东西... - Andrew Wolfe
显示剩余2条评论

-1

1
我不认同你在这个问题上得到的“-1”分,但你需要提供更多的讨论! - Andrew Wolfe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接