Oracle:根据数据集替换选项字符串 - 这可能吗?

Oracle : replace string of options based on data set - is this possible?

我在 table 中的列如下所示:

PATTERN
{([option1]+[option2])*([option3]+[option4])}
{([option1]+[option2])*([option3]+[option4])*([option6]+[option7])}
{[option1]+[option6]}
{([option1]+[option2])*([option8]+[option9])}
{([option1]+[option2])*[option4]}
{[option10]}

每个选项都有多个值。 有一个 table - 我们称它为 option_set 并且记录看起来像

OPTION      VALUE
option1     3653265
option2     26452
option3     73552
option3     100
option4     1235
option5     42565
option6     2330
option7     544
option9     2150

我想将第一个 table 中的选项名称替换为编号,当然如果存在,如果不存在则 =0。 我在 PLSQL 中完成了此操作(获取模式,检查每个选项,如果存在 - regexp_replace), 但我想知道这是否可以在 SQL 中完成?? 我的目标是替换当前 OPTION_SET 的所有模式的值并仅获取所有方程都大于 0 的记录。当然 - 我不能 运行 SQL 中的这个方程, 所以我想到了

for rec in
(
  SELECT...
)
loop
  execute immediate '...';
  if above_equation > 0 then ..
end loop;

如有任何想法,我们将不胜感激

您可以在 SQL 中使用递归 CTE 执行类似循环的查询,在每次迭代时替换新标记,这样您就可以替换所有标记。 我知道在 Oracle 中的 SQL 语句中执行动态查询的唯一方法是 DBMS_XMLGEN 包,因此您可以在没有 PL/SQL 的情况下计算表达式并按结果值过滤。但是所有这些对于具有模式和选项的低基数表都是可行的。

代码如下:

with a as (
  select 1 as id, '{([option1]+[option2])*([option3]+[option4])}' as pattern from dual union all
  select 2 as id, '{([option1]+[option2])*([option3]+[option4])*([option6]+[option7])}' as pattern from dual union all
  select 3 as id, '{[option1]+[option6]}' as pattern from dual union all
  select 4 as id, '{([option1]+[option2])*([option8]+[option9])}' as pattern from dual union all
  select 5 as id, '{([option1]+[option2])*[option4]}' as pattern from dual union all
  select 6 as id, '{[option10]}]' as pattern from dual
)
, opt as (
  select 'option1' as opt, 3653265 as val from dual union all
  select 'option2' as opt, 26452 as val from dual union all
  select 'option3' as opt, 73552 as val from dual union all
  select 'option3' as opt, 100 as val from dual union all
  select 'option4' as opt, 1235 as val from dual union all
  select 'option5' as opt, 42565 as val from dual union all
  select 'option6' as opt, 2330 as val from dual union all
  select 'option7' as opt, 544 as val from dual union all
  select 'option9' as opt, 2150 as val from dual
)
, opt_ordered as (
  /*Order options to iterate over*/
  select opt.*, row_number() over(order by 1) as rn
  from opt
)
, rec (id, pattern, repl_pattern, lvl) as (
  select
    id,
    pattern,
    pattern as repl_pattern,
    0 as lvl
  from a
  
  union all
  
  select
    r.id,
    r.pattern,
    /*Replace each part at new step*/
    replace(r.repl_pattern, '[' || o.opt || ']', o.val),
    r.lvl + 1
  from rec r
    join opt_ordered o
      on r.lvl + 1 = o.rn
)
, out_prepared as (
  select
    rec.*,
    case
      when instr(repl_pattern, '[') = 0
      /*When there's no more not parsed expressions, then we can try to evaluate them*/
      then dbms_xmlgen.getxmltype(
        'select ' || replace(replace(repl_pattern, '{', ''), '}', '')
        || ' as v from dual'
      )
      /*Otherwise SQL statement will fail*/
    end as parsed_expr
  from rec
  /*Retrieve the last step*/
  where lvl = (select max(rn) from opt_ordered)
)
select
  id,
  pattern,
  repl_pattern,
  extractvalue(parsed_expr, '/ROWSET/ROW/V') as calculated_value
from out_prepared o
where extractvalue(parsed_expr, '/ROWSET/ROW/V') > 0
ID | PATTERN                                                             | REPL_PATTERN                              | CALCULATED_VALUE
-: | :------------------------------------------------------------------ | :---------------------------------------- | :---------------
 1 | {([option1]+[option2])*([option3]+[option4])}                       | {(3653265+26452)*(73552+1235)}            | 275194995279    
 2 | {([option1]+[option2])*([option3]+[option4])*([option6]+[option7])} | {(3653265+26452)*(73552+1235)*(2330+544)} | 790910416431846 
 3 | {[option1]+[option6]}                                               | {3653265+2330}                            | 3655595         
 5 | {([option1]+[option2])*[option4]}                                   | {(3653265+26452)*1235}                    | 4544450495      

db<>fiddle here

这是一种方法。有很多东西要打开,所以要坚持。

我在 with 子句中包含了测试数据。当然,您不需要那个;只需删除两个“tables”并在查询中使用您实际的 table 和列名称。

从 Oracle 12.1 开始,我们可以直接在顶部的 with 子句中定义 PL/SQL 函数;如果我们这样做,查询必须以斜线 (/) 而不是通常的分号 (;) 结束。如果你的版本低于12.1,你可以单独定义函数。我使用的函数采用“算术表达式”(表示复合算术运算的字符串)和 returns 其值作为数字。它使用本机动态 SQL(“立即执行”语句),这将导致查询相对较慢,因为为每一行解析不同的游标。如果速度成为问题,这可以改变,使用绑定变量(这样游标只被解析一次)。

with 子句中的递归查询将每个占位符替换为“选项”table 的相应值。如果“占位符”在 table 中没有相应的选项,或者如果有但相应的值为 null,我将使用 0。 (请注意,您的示例数据显示 option3 两次;这没有意义,我从示例数据中删除了一次。)

我没有一次替换一个占位符,而是采用了相反的方法;假设模式可能很长,但“选项”的数量很少,这应该更有效。即:在每个步骤中,我一次性替换所有出现的 '[optionN]'(对于给定的 N)。在递归查询之外,我将“不存在”选项的所有占位符替换为 0.

请注意,递归 with 子句需要 Oracle 11.2。如果你的版本比那个更早(虽然不应该),还有其他方法;您可能还需要在 PL/SQL 中这样做。

所以,这里是 - 一个 SELECT 查询整个事情:

with
  function expr_eval(pattern varchar2) return number as
    x number;
  begin
    execute immediate 'select ' || pattern || ' from dual' into x;
    return x;
  end;
  p (id, pattern) as (
    select 1, '{([option1]+[option2])*([option3]+[option4])}'                       from dual union all
    select 2, '{([option1]+[option2])*([option3]+[option4])*([option6]+[option7])}' from dual union all
    select 3, '{[option1]+[option6]}'                                               from dual union all
    select 4, '{([option1]+[option2])*([option8]+[option9])}'                       from dual union all
    select 5, '{([option1]+[option2])*[option4]}'                                   from dual union all
    select 6, '{[option10]}'                                                        from dual union all
    select 7, '{[option2]/([option3]+[option8])-(300-[option2])/(0.1 *[option3])}'  from dual
  )
, o (opt, val) as (
    select 'option1', 3653265 from dual union all
    select 'option2',   26452 from dual union all
    select 'option3',     100 from dual union all
    select 'option4',    1235 from dual union all
    select 'option5',   42565 from dual union all
    select 'option6',    2330 from dual union all
    select 'option7',     544 from dual union all
    select 'option9',    2150 from dual
  )
, n (opt, val, rn, ct) as (
    select opt, val, rownum, count(*) over ()
    from   o
  )
, r (id, pattern, rn, ct) as (
    select id, substr(pattern, 2, length(pattern) - 2), 1, null
      from p
    union all
    select r.id, replace(r.pattern, '[' || n.opt || ']', nvl(to_char(n.val), 0)),
           r.rn + 1, n.ct
      from r join n on r.rn = n.rn
  )
, ae (id, pattern) as (
    select id, regexp_replace(pattern, '\[[^]]*]', '0')
    from   r
    where  rn = ct + 1
  ) 
select id, expr_eval(pattern) as result 
from   ae
order  by id
/

输出:

  ID          RESULT
---- ---------------
   1      4912422195
   2  14118301388430
   3         3655595
   4      7911391550
   5      4544450495
   6               0
   7         2879.72