Oracle REGEXP_LIKE 字符串中子字符串的逻辑和匹配

Oracle REGEXP_LIKE logical and matching of substrings in string

我有一个包含 'code1 code2 code3' 等代码的字符串。如果输入的所有代码都包含在字符串中,它应该 return 字符串。

例如:

select * from (
select 'avs cde jkl' code from dual)
where REGEXP_LIKE(code, 'REGEX-MAGIC') 

当正则表达式现在类似于 ^(?=.*\bjkl\b)(?=.*\bavs\b).*$ 时,它应该 return 代码。但是这种语法不适用于 oracle 中的正则表达式。

逻辑是'if all codes looked for are in the string (order does not matter), then return the code.'

我已经研究过,这可以通过积极的前瞻来实现,但据我所知,oracle 不支持这一点。我会搜索一个正则表达式,而不是像 REGEXP_LIKE(...,..) and REGEXP_LIKE(...,..) and ....

这样的结构

Oracle 版本为 12c。

如有任何帮助,我们将不胜感激!

我不擅长 regex-magix,但是 - 看看这样的事情是否有帮助。

这是一个包含这些代码的 table:

SQL> select * from codes;

        ID CODE
---------- -----------
         1 avs cde jkl
         2 xyz avs

查询

  • 将每个代码拆分成行(t_split CTE)
  • 对输入的参数 (par_string) 值 (p_split CTE) 做同样的事情
  • 为什么?这样它们就可以像 table 中的行一样工作,您可以应用 MINUS set operator
  • if MINUS returns nothing, 匹配;否则就是不匹配

SQL> with
  2  -- split code to rows
  3  t_split as
  4    (select id,
  5            code original_code,
  6            regexp_substr(code, '[^ ]+', 1, column_value) code
  7      from codes cross join
  8        table(cast(multiset(select level from dual
  9                            connect by level <= regexp_count(code, ' ') + 1
 10                           ) as sys.odcinumberlist))
 11      where id = &&par_id
 12    ),
 13  -- split parameter to rows
 14  p_split as
 15    (select regexp_substr('&&par_string', '[^ ]+', 1, level) code
 16     from dual
 17     connect by level <= regexp_count('&&par_string', ' ') + 1
 18    )
 19  --
 20  -- if all parameter's "pieces" of code are contained in CODE value, MINUS returns nothing
 21  -- so there's a match
 22  select distinct t.original_code,
 23         '&&par_string' par_string,
 24         case when (select count(*)
 25                    from (select code from t_split
 26                          minus
 27                          select code from p_split
 28                         )
 29                   ) = 0 then 'Match'
 30              else 'Mismatch'
 31         end result
 32  from t_split t
 33  where t.id = &&par_id;

Enter value for par_id: 1
Enter value for par_string: jkl avs cde

ORIGINAL_CO PAR_STRING  RESULT
----------- ----------- --------
avs cde jkl jkl avs cde Match

SQL> undefine par_string
SQL> /
Enter value for par_string: avs jkl www

ORIGINAL_CO PAR_STRING  RESULT
----------- ----------- --------
avs cde jkl avs jkl www Mismatch

SQL>

根据您使用的工具(这是 SQL*Plus),您可能需要将 && 替换为冒号 :;或者,将这样一段代码转换为函数。

Oracle 不支持 look-ahead、look-behind 或正则表达式中的单词边界。

如果您有示例数据:

CREATE TABLE table_name (code) AS
SELECT 'avs cde jkl' FROM DUAL UNION ALL
SELECT 'avs cde'     FROM DUAL UNION ALL
SELECT 'jkl avs'     FROM DUAL UNION ALL
SELECT 'cde jkl'     FROM DUAL;

选项 1:

最简单的查询是不使用正则表达式并使用多个 LIKE 条件查找 sub-string 匹配项:

SELECT code
FROM   table_name
WHERE  ' ' || code || ' ' LIKE '% avs %'
AND    ' ' || code || ' ' LIKE '% jkl %'

输出:

CODE
avs cde jkl
jkl avs

选项 2:

您可以在多个 REGEXP_LIKE 条件下使用(较慢的)正则表达式:

SELECT code
FROM   table_name
WHERE  REGEXP_LIKE(code, '(^| )avs( |$)')
AND    REGEXP_LIKE(code, '(^| )jkl( |$)')

输出与上面相同。

选项 3:

您可以将匹配放入 sub-query 分解子句,然后使用 LATERAL 连接:

WITH match_conditions (match) AS (
  SELECT 'avs' FROM DUAL UNION ALL
  SELECT 'jkl' FROM DUAL
)
SELECT code
FROM   table_name t
       CROSS JOIN LATERAL (
         SELECT 1
         FROM   match_conditions
         WHERE  ' ' || code || ' ' LIKE '% ' || match || ' %'
         HAVING COUNT(*) = (SELECT COUNT(*) FROM match_conditions)
       )

输出与上面相同。

选项 4:

如果您真的想要一个正则表达式,那么您可以生成要匹配的代码的每个排列并将它们连接成一个正则表达式:

SELECT code
FROM   table_name
WHERE  REGEXP_LIKE(
         code,
             '(^| )avs( | .*? )jkl( |$)' -- Permutation 1
         || '|(^| )jkl( | .*? )avs( |$)' -- Permutation 2
       )

输出与上面相同。

但是,随着要匹配的代码数量的增加,维护起来会出现问题,因为 2 个项目有 2 个排列,但 5 个项目有 5 个排列! = 120 个排列。

选项 5:

您可以声明一个嵌套的 table 集合:

CREATE TYPE string_list AS TABLE OF VARCHAR2(20);

然后拆分字符串(同样,您不需要慢速正则表达式)然后将其与嵌套的 table:

进行比较
WITH bounds (rid, code, spos, epos) AS (
  SELECT ROWID, code, 1, INSTR(code, ' ', 1)
  FROM   table_name
UNION ALL
  SELECT rid, code, epos + 1, INSTR(code, ' ', epos + 1)
  FROM   bounds
  WHERE  epos > 0
)
SEARCH DEPTH FIRST BY code SET order_rn
SELECT MAX(code) AS code
FROM   bounds
GROUP BY rid
HAVING string_list('avs', 'jkl') SUBMULTISET OF CAST(
         COLLECT(
           CAST(
             CASE epos
             WHEN 0
             THEN SUBSTR(code, spos)
             ELSE SUBSTR(code, spos, epos - spos)
             END
             AS VARCHAR2(20)
           )
         )
         AS string_list
       );

根据您使用的客户端应用程序,您可以将整个 string_list('avs', 'jkl') 集合作为单个绑定变量传入,您可以从数组中填充该变量。 Java(以及一些建立在 Java 之上的语言)使用 ODBC 驱动程序可以做到这一点; C# 不能直接传递,但您可以传递关联数组并将其转换为带有辅助函数的嵌套 table 集合。

输出与上面相同。

db<>fiddle here