【问题标题】:Evaluate a string as condition in Oracle在 Oracle 中评估字符串作为条件
【发布时间】:2022-01-02 16:02:20
【问题描述】:

例如,如果我有一个类似的字符串

my_string := ' ''a'' = ''a'' and 1 > 0 '

我可以在过程/函数中执行类似的操作来评估它

execute immediate 'select CASE WHEN(' || my_string || ') THEN 1 ELSE 0 END from dual'

但是有没有办法在不使用立即执行的情况下做到这一点?有没有办法像在查询中一样评估字符串?

我想要这样做是因为我在“COD1 like '%x%' OR COD2 = 'Z'”这样的表中有通用条件。所以我用这个字符串做了一些替换,但是我想让它们用 costraint 进行评估,以不使用用户定义的函数,所以没有“立即执行”

【问题讨论】:

    标签: sql string oracle oracle11g conditional-statements


    【解决方案1】:

    是的,但是...实际上您必须编写自己的表达式解析器:

    如果你有桌子:

    CREATE TABLE table_name (a, b, c, d) AS
    SELECT 'x', 'x', 'x', 'x' FROM DUAL UNION ALL
    SELECT 'w', 'x', 'y', 'z' FROM DUAL;
    
    CREATE TABLE filters (filter) AS
    SELECT 'a = b AND c <= d' FROM DUAL UNION ALL
    SELECT 'a < b AND b < c AND c < d' FROM DUAL UNION ALL
    SELECT 'a < ''y''' FROM DUAL UNION ALL
    SELECT 'c LIKE ''%y%''' FROM DUAL;
    

    并且您想将filters 应用到table_name 然后,从Oracle 12,您可以使用:

    WITH split_filters ( id, filter, left_operand, operator, right_operand, expr, num_expr ) AS (
      SELECT ROWID,
             filter,
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               1
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               3
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               4
             ),
             1,
             REGEXP_COUNT(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               'i'
             )
      FROM   filters
    UNION ALL
      SELECT id,
             filter,
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               1
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               3
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               4
             ),
             expr + 1,
             num_expr
      FROM   split_filters
      WHERE  expr < num_expr
    )
    SELECT *
    FROM   table_name t
           CROSS JOIN LATERAL (
             SELECT MAX(filter) AS filter
             FROM   (
               SELECT id,
                      filter,
                      CASE 
                      WHEN UPPER(left_operand) = 'A' THEN t.a
                      WHEN UPPER(left_operand) = 'B' THEN t.b
                      WHEN UPPER(left_operand) = 'C' THEN t.c
                      WHEN UPPER(left_operand) = 'D' THEN t.d
                      WHEN left_operand LIKE '''%''' THEN REPLACE(SUBSTR(left_operand, 2, LENGTH(left_operand) - 2), '''''', '''')
                      END AS l_op,
                      operator AS op,
                      CASE 
                      WHEN UPPER(right_operand) = 'A' THEN t.a
                      WHEN UPPER(right_operand) = 'B' THEN t.b
                      WHEN UPPER(right_operand) = 'C' THEN t.c
                      WHEN UPPER(right_operand) = 'D' THEN t.d
                      WHEN right_operand LIKE '''%''' THEN REPLACE(SUBSTR(right_operand, 2, LENGTH(right_operand) - 2), '''''', '''')
                      END AS r_op,
                      num_expr
               FROM   split_filters
             )
             WHERE CASE 
                   WHEN op = '='    AND l_op =  r_op THEN 1
                   WHEN op = '!='   AND l_op != r_op THEN 1
                   WHEN op = '<'    AND l_op <  r_op THEN 1
                   WHEN op = '>'    AND l_op >  r_op THEN 1
                   WHEN op = '<='   AND l_op <= r_op THEN 1
                   WHEN op = '>='   AND l_op >= r_op THEN 1
                   WHEN op = 'LIKE' AND l_op LIKE r_op THEN 1
                   END = 1
              GROUP BY id
              HAVING COUNT(*) = MAX(num_expr)
           );
    

    哪些输出:

    A B C D FILTER
    x x x x a = b AND c <= d
    x x x x a < 'y'
    w x y z a < b AND b < c AND c < d
    w x y z a < 'y'
    w x y z c LIKE '%y%'

    db小提琴here


    在 Oracle 11g 中,您可以将其重写为:

    WITH split_filters ( id, filter, left_operand, operator, right_operand, expr, num_expr ) AS (
      SELECT ROWID,
             filter,
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               1
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               3
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               1,
               'i',
               4
             ),
             1,
             REGEXP_COUNT(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               'i'
             )
      FROM   filters
    UNION ALL
      SELECT id,
             filter,
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               1
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               3
             ),
             REGEXP_SUBSTR(
               filter,
               '(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)'       -- left_operand
               || '\s*([<>!]?=|[<>]|LIKE)'                 -- operator
               || '\s*(''([^'']|'''')*''|[A-Z][A-Z0-9_]*)' -- right_operand
               || '\s*($|\sAND\s+)',                       -- expression concatenator
               1,
               expr + 1,
               'i',
               4
             ),
             expr + 1,
             num_expr
      FROM   split_filters
      WHERE  expr < num_expr
    ),
    operand_substitutions (t_id, f_id, a, b, c, d, filter, l_op, op, r_op, num_expr) AS (
      SELECT t.ROWID,
             f.id,
             t.a,
             t.b,
             t.c,
             t.d,
             filter,
             CASE 
             WHEN UPPER(left_operand) = 'A' THEN t.a
             WHEN UPPER(left_operand) = 'B' THEN t.b
             WHEN UPPER(left_operand) = 'C' THEN t.c
             WHEN UPPER(left_operand) = 'D' THEN t.d
             WHEN left_operand LIKE '''%''' THEN REPLACE(SUBSTR(left_operand, 2, LENGTH(left_operand) - 2), '''''', '''')
             END,
             operator,
             CASE 
             WHEN UPPER(right_operand) = 'A' THEN t.a
             WHEN UPPER(right_operand) = 'B' THEN t.b
             WHEN UPPER(right_operand) = 'C' THEN t.c
             WHEN UPPER(right_operand) = 'D' THEN t.d
             WHEN right_operand LIKE '''%''' THEN REPLACE(SUBSTR(right_operand, 2, LENGTH(right_operand) - 2), '''''', '''')
             END,
             num_expr
      FROM   split_filters f
             CROSS JOIN table_name t
    )
    SELECT MAX(a) AS a,
           MAX(b) AS b,
           MAX(c) AS c,
           MAX(d) AS d,
           MAX(filter) AS filter
    FROM   operand_substitutions
    WHERE  CASE 
           WHEN op = '='    AND l_op =  r_op THEN 1
           WHEN op = '!='   AND l_op != r_op THEN 1
           WHEN op = '<'    AND l_op <  r_op THEN 1
           WHEN op = '>'    AND l_op >  r_op THEN 1
           WHEN op = '<='   AND l_op <= r_op THEN 1
           WHEN op = '>='   AND l_op >= r_op THEN 1
           WHEN op = 'LIKE' AND l_op LIKE r_op THEN 1
           END = 1
    GROUP BY t_id, f_id
    HAVING COUNT(*) = MAX(num_expr);
    

    db小提琴here

    【讨论】:

    • 很有趣,我也有 IN 子句和 OR 和 AND 连接,所以我应该改进解析器
    • @DomenicoF。添加的越多,实现就越困难。 IN 右侧有文字并不太难;但是,右侧有列的IN 要困难得多。 OR 增加了优先级的复杂性,然后如果您在逻辑运算符周围添加() 大括号以更改优先级,那么它会变得更加复杂。最简单的解决方案是将动态 SQL 与 EXECUTE IMMEDIATE 一起使用,而不必自己解析。
    【解决方案2】:

    有没有办法在不使用立即执行的情况下做到这一点

    您可以使用替代变量作为替代方法,例如

    SQL> SELECT CASE WHEN(&str) THEN 1 ELSE 0 END
      2    FROM dual; 
    
    CASEWHEN('A'='A'AND1>0)THEN1EL
    ------------------------------
                                 1
    

    'a' = 'a' and 1 &gt; 0 在出现提示时输入 &amp;str 的位置

    【讨论】:

    • 是的,但我在计划的工作中需要它,不能使用提示
    【解决方案3】:

    没办法,据我所知。这就是动态 SQL(即execute immediate)的用途。


    例如,如果您只将一个条件(为简单起见)放入表中:

    SQL> select * from test;
    
    MY_STRING
    ---------------------
     'a' = 'a' and 1 > 0
    

    并将其交叉连接到另一个表(因为,我希望 everything 在该条件始终满足时返回),您会收到错误:

    SQL> select *
      2  from dept d cross join test t
      3  where t.mystring;
    where t.mystring
                   *
    ERROR at line 3:
    ORA-00920: invalid relational operator
    

    while - 如果将条件按字面意思放入where 子句中,它有效

    SQL> select *
      2  from dept d cross join test t
      3  where 'a' = 'a' and 1 > 0;
    
        DEPTNO DNAME          LOC           MY_STRING
    ---------- -------------- ------------- ---------------------
            10 ACCOUNTING     NEW YORK       'a' = 'a' and 1 > 0
            20 RESEARCH       DALLAS         'a' = 'a' and 1 > 0
            30 SALES          CHICAGO        'a' = 'a' and 1 > 0
            40 OPERATIONS     BOSTON         'a' = 'a' and 1 > 0
    
    SQL>
    

    所以,恐怕是动态 SQL。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-06
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多