正则表达式:如何在 PL/SQL 中实现负向后查找

Posted

技术标签:

【中文标题】正则表达式:如何在 PL/SQL 中实现负向后查找【英文标题】:Regex: How to Implement Negative Lookbehind in PL/SQL 【发布时间】:2014-11-12 21:34:23 【问题描述】:

如何匹配所有以loockup. 开头并以_id 结尾但不以msg 为前缀的字符串?下面是一些例子:

lookup.asset_id -> should match
lookup.msg_id -> shouldn't match
lookup.whateverelse_id -> should match

我知道 Oracle 不支持负向回溯(即(?<!))...所以我尝试使用交替显式枚举可能性:

regexp_count('i_asset := lookup.asset_id;', 'lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id') <> 0 then
    dbms_output.put_line('match'); -- this matches as expected
end if;

regexp_count('i_msg := lookup.msg_id;', 'lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id') <> 0 then
    dbms_output.put_line('match'); -- this shouldn’t match
                                   -- but it does like the previous example... why?
end if;

第二个regexp_count 表达式不应该匹配...但它确实像第一个。我错过了什么吗?

编辑

在实际用例中,我有一个包含 PL/SQL 代码的字符串,其中可能包含多个 lookup.xxx_id 实例:

declare
    l_source_code varchar2(2048) := '
        ...
        curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');
        asset : = lookup.asset_id(key_val => ''UBSN''); -- this is wrong since it does
                                                        -- not specify key_type
        ...
        msg := lookup.msg_id(key_val => ''hello''); -- this is fine since msg_id does
                                                    -- not require key_type
    ';
    ...
 end;

我需要确定是否至少有一个错误的lookup,即除了lookup.msg_id之外的所有出现,还必须指定key_type参数。

【问题讨论】:

是单个字符串还是多个字符串的串联?可以有很多级别,例如 'lookup.tunnel.east.msg_id' 'lookup.tunnel.east.alternative_msg_id' 呢?有很多方法可以做到这一点,但需要更多信息。 不,只有一层。始终为lookup.xyz_id(param1, param2, paramN) 【参考方案1】:

使用lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id,您基本上是在要求检查字符串

    lookup.开头,用lookup\.表示, 后跟至少一个不同于( 的字符,用[^\(]+ 表示, 后跟 -- ( | | ) 一个不同于m的字符--[^m],或者 两个字符:m 加上没有s -- m[^s],或 三个字符:ms,没有g——ms[^g],和 以_id 结尾,用_id 表示。

因此,对于lookup.msg_id,第一部分显然匹配,第二部分消耗ms,而将g 留给第三部分的第一个替代项。

这可以通过将第三部分修补为始终为三个字符长(如lookup\.[^\(]+([^m]..|m[^s.]|ms[^g])_id)来解决。然而,这会导致一切都失败,lookup._id 之间的部分长度至少为四个字符:

WITH
Input (s, r) AS (
  SELECT 'lookup.asset_id', 'should match' FROM DUAL UNION ALL
  SELECT 'lookup.msg_id', 'shouldn''t match' FROM DUAL UNION ALL
  SELECT 'lookup.whateverelse_id', 'should match' FROM DUAL UNION ALL
  SELECT 'lookup.a_id', 'should match' FROM DUAL UNION ALL
  SELECT 'lookup.ab_id', 'should match' FROM DUAL UNION ALL
  SELECT 'lookup.abc_id', 'should match' FROM DUAL
)
SELECT
  r, s, INSTR(s, 'lookup.msg_id') has_msg, REGEXP_COUNT(s , 'lookup\.[^\(]+([^m]..|m[^s]|ms[^g])_id') matched FROM Input
;

|               R |                      S | HAS_MSG | MATCHED |
|-----------------|------------------------|---------|---------|
|    should match |        lookup.asset_id |       0 |       1 |
| shouldn't match |          lookup.msg_id |       1 |       0 |
|    should match | lookup.whateverelse_id |       0 |       1 |
|    should match |            lookup.a_id |       0 |       0 |
|    should match |           lookup.ab_id |       0 |       0 |
|    should match |          lookup.abc_id |       0 |       0 |

如果您只是要确保在相关职位中没有msg,您可能想要 (INSTR(s, 'lookup.msg_id') = 0) AND REGEXP_COUNT(s, 'lookup\.[^\(]+_id') &lt;&gt; 0

为了代码清晰,REGEXP_INSTR(s, 'lookup\.[^\(]+_id') &gt; 0 可能更可取……

@j3d 如果需要更多详细信息,请发表评论。

【讨论】:

感谢您的好评。如果我在同一个字符串中出现更多 lookup.xxx_id 怎么办?在现实世界中,我需要检查可能包含任意数量的匹配或不匹配 lookup 实例的源代码。 @j3d 你能提供更多细节吗?特别是:除了lookup.msg_id,还有更多的“黑名单”令牌吗?如果字符串同时包含要匹配和不匹配的标记,那么预期的响应是什么?检查的更大目的是什么? @j3d 我说得对吗:(i) 最初,您想挑选出msg 令牌以便不应用keytypecheck? (ii) 您需要检查语义而不是语法合规性 - 换句话说:例如,不会缺少引号,但 key_type 参数可能会丢失? (iii) 对要遵守的值的任何其他限制(大小写、特殊字符(不允许)等)? 只有在lookup. 后跟msg_id 时,才可能缺少key_type 参数。对于任何其他情况,必须指定 key_type 参数(或者必须指定至少两个参数)。 @j3d 我猜,您的更新中asset : = 中的第二个空白有误?【参考方案2】:

要求仍然有点模糊......

    在分号处拆分字符串。

    检查每个子字符串s 是否符合:

    WITH Input (s) AS (
      SELECT '   curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');' FROM DUAL UNION ALL
      SELECT 'curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');' FROM DUAL UNION ALL
      SELECT 'asset := lookup.asset_id(key_val => ''UBSN'');' FROM DUAL UNION ALL
      SELECT 'msg := lookup.msg_id(key_val => ''hello'');' FROM DUAL
    )
    SELECT
      s
    FROM Input
    WHERE REGEXP_LIKE(s, '^\s*[a-z]+\s+:=\s+lookup\.msg_id\(key_val => ''[a-zA-Z0-9]+''\);$')
     OR
     ((REGEXP_INSTR(s, '^\s*[a-z]+\s+:=\s+lookup\.msg_id') = 0)
      AND (REGEXP_INSTR(s, '[(,]\s*key_type') > 0)
      AND (REGEXP_INSTR(s,
        '^\s*[a-z]+\s+:=\s+lookup\.[a-z]+_id\(( ?key_[a-z]+ => ''[a-zA-Z_]+?'',?)+\);$') > 0)) 
    ;
    
    
    |                                                                        S |
    |--------------------------------------------------------------------------|
    |[tab] curry := lookup.curry_id(key_val => 'CHF', key_type => 'asset_iso');|
    |      curry := lookup.curry_id(key_val => 'CHF', key_type => 'asset_iso');|
    |                                 msg := lookup.msg_id(key_val => 'hello');|
    

这将允许右括号之前的多余逗号。但是如果输入在语法上是正确的,那么这样的逗号就不会存在。

【讨论】:

以上是关于正则表达式:如何在 PL/SQL 中实现负向后查找的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 中的负向后等价

JS正则向前查找和向后查找

如何在 PL/sql 中实现 NegEx?

在 oracle 中使用正则表达式查找 POBOX - PL/SQL

PL/SQL:通过正则表达式查找所有西里尔文(或非拉丁文)符号

正则表达式背后的 pl/sql 否定查看