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

Posted

技术标签:

【中文标题】Oracle:根据数据集替换选项字符串 - 这可能吗?【英文标题】:Oracle : replace string of options based on data set - is this possible? 【发布时间】:2021-01-12 14:11:37 【问题描述】:

表格中的列如下所示:

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

每个选项都有一些价值。 有一个表 - 我们称它为 option_set,记录看起来像

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

我想将选项名称替换为第一个表中的数字,如果存在当然,如果不存在则=0。 我已经在 PLSQL 中完成了这个(获取模式,遍历每个选项,如果存在 - regexp_replace), 但我想知道这是否可以在 SQL 中完成? 我的目标是替换当前 OPTION_SET 的所有模式的值并仅获取所有方程式都大于 0 的记录。当然 - 我无法在 SQL 中运行这个方程式,所以我想到了类似

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

任何想法将不胜感激

【问题讨论】:

是否包围了整个表达式而不出现在表达式本身中? 是的,它们是 和 ,在这个过程中它们被剪掉了。如果您有一些不包含括号的想法,请随时删除它们 您的“选项”表有两次选项 3。这没有任何意义。 我的错误。对不起 【参考方案1】:

您可以在 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
身份证 |图案 | REPL_PATTERN | CALCULATED_VALUE -: | :------------------------------------------------ ------------------ | :---------------------------------------------------- | :--------------- 1 | ([选项1]+[选项2])*([选项3]+[选项4]) | (3653265+26452)*(73552+1235) | 275194995279 2 | ([option1]+[option2])*([option3]+[option4])*([option6]+[option7]) | (3653265+26452)*(73552+1235)*(2330+544) | 790910416431846 3 | [选项1]+[选项6] | 3653265+2330 | 3655595 5 | ([选项1]+[选项2])*[选项4] | (3653265+26452)*1235 | 4544450495

db小提琴here

【讨论】:

太棒了!完美的!一个问题——你能向我解释一下递归查询吗?这是如何工作的,或者你知道一些好的教程吗? @q4za4 在in the documentation 上有一个简短的描述。在那篇文章的最后,你可以找到一些例子。简而言之:有一个 anchor 成员,它创建基础数据,递归从这里开始,还有一个 recursive member,它在 from 中使用相同的 with 名称或join 子句增加递归步骤。在每一步,由递归成员生成的数据被添加到该with 子查询的现有数据中。当新步骤没有新数据时,递归停止。【参考方案2】:

这是执行此操作的一种方法。有很多东西要打开,所以请坚持住。

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

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

with 子句中的递归查询将每个占位符替换为“选项”表的相应值。如果“占位符”在表中没有对应的选项,或者有但对应的值为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

【讨论】:

太棒了!完美的!一个问题——你能向我解释一下递归查询吗?这是如何工作的,或者你知道一些好的教程吗? @q4za4 - 要查看它的作用,请在r 的定义之后添加select * from r(并注释掉查询的其余部分,从ae 开始)。这将向您展示递归查询的作用。然后查看ae 的定义,看看我们真正想要从r 得到什么。除此之外,您还需要阅读有关递归查询的内容 - 此处无法仅在简短评论中对其进行解释。

以上是关于Oracle:根据数据集替换选项字符串 - 这可能吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据替换字符串在 Oracle Apex 中调整安装脚本?

SQL替换字段最后一个字符和几个字符

替换整个 DataFrame 中的字符串/值

Oracle 中临时数据的性能注意事项

我如何在 quicksight 中刷新 csv 数据集而不是替换数据集,因为这会丢失我的计算

oracle 优化 instr 替换 =