在 Oracle 中评估字符串作为条件

Evaluate a string as condition in Oracle

例如,如果我有一个像

这样的字符串
my_string := ' ''a'' = ''a'' and 1 > 0 '

我可以在 procedure/function

中做这样的事情来评估它
execute immediate 'select CASE WHEN(' || my_string || ') THEN 1 ELSE 0 END from dual'

但是有没有不用立即执行的方法呢?有没有办法像在查询中写入的那样评估字符串?

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

不可能,据我所知。这就是动态 SQL(即 execute immediate)的用途。


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

SQL> select * from test;

MY_STRING
---------------------
 'a' = 'a' and 1 > 0

并将它交叉连接到另一个 table (因为,我希望 所有东西 都返回,因为总是满足该条件),你会得到一个错误:

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。

is there a way to do that without using execute immediate

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

SQL> SELECT CASE WHEN(&str) THEN 1 ELSE 0 END
  2    FROM dual; 

CASEWHEN('A'='A'AND1>0)THEN1EL
------------------------------
                             1

其中 'a' = 'a' and 1 > 0 在出现提示时输入 &str

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

如果你有表格:

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<>fiddle 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<>fiddle here