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

Posted

技术标签:

【中文标题】为啥非贪心量词有时在 Oracle 正则表达式中不起作用?【英文标题】:Why doesn't a non-greedy quantifier sometimes work in Oracle regex?为什么非贪心量词有时在 Oracle 正则表达式中不起作用? 【发布时间】:2013-05-18 03:33:55 【问题描述】:

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,。为什么?


更新 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:

是的,这似乎是一个错误。

Oracle 支持人员对此问题有何反应?

这个错误是已知的吗?有身份证吗?

【问题讨论】:

是的,it's like that。请改用'.+B=.*?,''.*B=.+?,' 或者更好,使用.*B=.0,?, Egor,你能解释一下为什么要准确地重用 Perl RE 语法吗?您是否希望在 Perl 中解决 RE,然后在 Oracle 中使用它们,或者反之亦然?我应该假设您想要“*”的 Perl 含义吗?而不是默认? @AndrewWolfe - 我不想要一些特别的东西。 Oracle 中? 的含义与 Perl RE 中的含义相同 :-) Perl 复合运算符 "*?"是两个有效的单字符运算符的序列。这些单字符操作符在 Perl “*?” 的同一个地方在语法上是有效的。是。所以解析器做出判断。一个好的语言规范会指定一个优先级或关联性来说明哪个是哪个。然而,POSIX 说它是未定义的(告诉我们使用括号)并且 Perl 手册也没有做出太多承诺。 【参考方案1】:

这是一个BUG!

你是对的,在 Perl 中,'A=1,B=2,C=3,' =~ /.*B=.*?,/; print $& 打印 A=1,B=2,

您偶然发现了一个仍然存在于 Oracle Database 11g R2 中的错误。如果完全相同的正则表达式原子(包括量词但不包括贪婪修饰符)在正则表达式中出现两次,则无论第二次指定的贪婪如何,两次出现都将具有第一次出现所指示的贪婪。这些结果清楚地证明了这是一个错误(这里,“完全相同的正则表达式原子”是[^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 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,

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

【讨论】:

嗯,是的,这个假设似乎确实成立:sqlfiddle.com/#!4/d41d8/11473 是的,它对我来说也像是一个错误。你能提供官方确认吗? 奇怪的是,通过交换 在 Q 中获得所需的结果?到第一个通配符位置,* 到后者:regexp_substr('A=1,B=2,C=3,', '.?B=.*,')。匹配“第一贪婪”的假设。 @OldPro - 感谢您理解我的问题的本质。事实上,你的回答对我来说并没有什么新意。我认为,获得赏金需要权威参考。 在第一段,你能添加完全相同的正则表达式运算符是什么(例如)吗?【参考方案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('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​​_@

【讨论】:

【参考方案3】:

您的赏金非常丰厚,所以我将尝试全面确定。

您在正则表达式处理中做出了不正确的假设。

    Oracle 与 Perl 正则表达式不兼容,它是 与 POSIX 兼容。 It describes its support for Perl as "Perl-Influenced" 在使用 Perl“*?”时存在内在语法冲突。在 Oracle 中,如果你 以我的方式阅读该参考资料,Oracle 合法地选择了 POSIX 用法 您对 perl 如何处理“*?”的描述不太对。

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

CASE SRC TEXT RE FROM_WHOM 结果 -------- ------------------- ------------ ------ ----- ---------------------------- ---------- -------------- 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 comment 1 form 1 A=1,B=2, 5 Egor的原始源字符串A=1,B=2,C=3, .*B=.0,?, 老专业评论2 A=1,B=2, 6 Egor的原始源字符串A=1,B=2,C=3, [^B]*B=[^Bx]*?, Old Pro answer form 1 "good" A=1,B=2, 7 Egor的原始源字符串A=1,B=2,C=3, [^B]*B=[^B]*?, Old Pro answer form 2 "bad" 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 Variant强制Perl操作A=1,B=2, 31 Egor的原始源字符串A=1,B=2,C=3,.*B=(.*)?, Schemaczar Variant of Egor to force POSIX A=1,B=2,C=3, 32 Egor 的原始源字符串 A=1,B=2,C=3, .*B=.*0,1 Schemaczar 应用 Egor 的 'non-greedy' 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,“*?”具有比“*”更低的优先级和关联性。所以甲骨文将其读作“(。*)?” (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,那赏金怎么样......漂亮吗? POSIX 规范:“多个相邻重复符号('+'、'*'、'?' 和间隔)的行为会产生未定义的结果。”请参阅pubs.opengroup.org/onlinepubs/009695399/basedefs/… 第 9.4.6 小节。 (需要免费注册。) Andrew,我不知道您如何阅读 Oracle 的文档,该文档描述了对 *? 作为非贪婪量词的支持,并得出结论 Oracle 合法地选择了 POSIX 用法。很明显,量词的优先级是? 紧密绑定到它后面的量词,以使前面的量词不贪婪。您的回答中没有任何内容可以解释 Old Pro(我的)示例的行为如何与 Oracle 暗示的任何规则集一致。你漏掉了我最喜欢的:.*?B=.*, 产生了A=1,B=2,,这不可能是正确的。 @OldPro,对不起,我错过了你的案子。我不同意 Oracle 明确了绑定优先级。你的想法是“?”更紧密地绑定是非常明智的,我只是没有看到 Oracle 在文档中以您的方式说明它。可以说这对我来说太温顺了,但是当 RE 没有给我想要的结果时,我开始添加括号,直到 RE 引擎并且我同意结果。我认为在甲骨文“*?”在括号中的前一个子表达式上比在括号外的子表达式上更一致地进行“非贪婪”工作。再次, ”?”在任何 subexpr 之后是合法的,所以我对待“*?”作为模棱两可和括号。 并不是说@EgorSkriptunoff 可能会因我们的麻烦而奖励我们中的任何人......【参考方案4】:

因为你选择的太多了:

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

【讨论】:

我不买你在这个上得到的“-1”勾号,但你必须提供比这更多的讨论!

以上是关于为啥非贪心量词有时在 Oracle 正则表达式中不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的正则表达式组量词不起作用?

正则进阶之,回溯, (贪婪* 非贪婪+? 独占++)三种匹配量词

正则表达式之量词

python-正则表达式

正则表达式量词 - 两个字符之间的数量

Python 正则表达式 贪心匹配和非贪心匹配