使用递归查询替换Oracle中的公式

问题描述 投票:0回答:1

我在 Oracle 中有以下两个表。

Create table Calc(CALC_CD VARCHAR(10), FORMULA1 VARCHAR(200), FORMULA2 VARCHAR(200), FORMULA3 VARCHAR(200), FORMULA4 VARCHAR(200));
Create table UnitData(UnitNumber VARCHAR(100), CALC_CD VARCHAR(10), GRP_CD VARCHAR(100), GRP_AMT DECIMAL);

insert into Calc VALUES('M10', 'SI01/VI01', 'SI02/VI02', 'Part1 * 0.3', 'Part2 + Part3');
    
insert into UnitData VALUES ('111E', 'M10', 'SI01', 233.23);
insert into UnitData VALUES ('111E', 'M10', 'VI01', 4.98);
insert into UnitData VALUES ('111E', 'M10', 'SI02', 233.23);
insert into UnitData VALUES ('111E', 'M10', 'VI02', 4.98);
insert into UnitData VALUES ('112E', 'M10', 'SI01', 133.23);
insert into UnitData VALUES ('112E', 'M10', 'VI01', 3.98);
insert into UnitData VALUES ('112E', 'M10', 'SI02', 133.23);
insert into UnitData VALUES ('112E', 'M10', 'VI02', 3.98);

And third result table which as below: 

Create table Calc_Result(UnitNumber VARCHAR(100), CALC_CD VARCHAR(10), Formula1 VARCHAR(200), Formula2 VARCHAR(200), Formula3 VARCHAR(200), Formula4 VARCHAR(200), FORMULA_RESULT DECIMAL)

结果将如下所示:

UnitNumber | Calc_CD | Formula1    | Formula2    | Formula3       | Formula4         | Formula_Result
111E       | M10     | 233.23/4.98 | 233.23/4.98 | 46.83 * 0.3    | 14.05 + 46.83    | 60.88 

现在,我使用多个游标完成所有这些计算。外部光标循环遍历每个单元,两个内部光标首先用实际金额替换组代码,然后计算每个公式的结果并输入到下一个公式。这是相当复杂的解决方案的简短版本,其中我最多有 10 个公式。有没有办法使用递归查询将所有公式中所有出现的“SI*”和“VI*”值替换为其实际数量? UnitData 是一个非常大的表,包含超过 32M 条记录,到目前为止,完成此任务需要 5 个多小时。我希望更换一些光标以加快该过程。

这里是带有示例数据的 db fiddle 的链接:DB_Fiddle

oracle
1个回答
0
投票

让我们用 SQL 编写一个解析器...(将我的答案改编为类似的问题来处理不仅仅是单字符术语和文字术语)

  1. 将调车场算法实现为递归子查询;然后
  2. 从调车场取出反向抛光输出并用它来计算价值。
WITH FUNCTION is_operator(term VARCHAR2)
  RETURN NUMBER
  IS
  BEGIN
    RETURN CASE
           WHEN term IS NULL THEN NULL
           WHEN term IN ('+ ', '- ', '/ ', '* ', '^ ') THEN 1
           ELSE 0
           END;
  END;
FUNCTION priority(operator VARCHAR2)
  RETURN NUMBER
  IS
  BEGIN
    RETURN CASE operator
           WHEN '+ ' THEN 1
           WHEN '- ' THEN 1
           WHEN '/ ' THEN 2
           WHEN '* ' THEN 2
           WHEN '^ ' THEN 3
           END;
  END;
calc1 (calc_cd, formula2, formula3, formula4) AS (
  SELECT calc_cd,
         REPLACE(formula2, 'Part1', formula1),
         REPLACE(formula3, 'Part1', formula1),
         REPLACE(formula4, 'Part1', formula1)
  FROM   calc
),
calc2 (calc_cd, formula3, formula4) AS (
  SELECT calc_cd,
         REPLACE(formula3, 'Part2', formula2),
         REPLACE(formula4, 'Part2', formula2)
  FROM   calc1
),
calc3 (calc_cd, formula) AS (
  SELECT calc_cd,
         REPLACE(formula4, 'Part3', formula3)
  FROM   calc2
),
shunting_yard (calc_cd, formula, input, stack, output) AS (
  SELECT calc_cd,
         formula,
         TRIM(
           REPLACE(
             REGEXP_REPLACE(
               formula,
               '\s*([()^*/+-])\s*',
               ' \1 '
             ),
             '  ',
             ' '
           )
         ) || ' ',
         CAST(NULL AS VARCHAR2(4000)),
         CAST(NULL AS VARCHAR2(4000))
  FROM   calc3
UNION ALL
  SELECT calc_cd,
         formula,
         CASE
         WHEN (f_term = ') ' AND s_term != '( ')
         THEN input
         WHEN f_term IN ('( ', ') ')
         OR   is_operator(f_term) = 0
         OR   stack IS NULL
         OR   s_term = '( '
         OR   priority(s_term) < priority(f_term)
         THEN SUBSTR(input, INSTR(input, ' ') + 1)
         ELSE input
         END,
         CASE
         WHEN f_term = '( '
         THEN f_term || stack
         WHEN f_term = ') '
         THEN SUBSTR(stack, INSTR(stack, ' ') + 1)
         WHEN is_operator(f_term) = 0
         THEN stack
         WHEN stack IS NULL
         OR   s_term = '( '
         OR   priority(s_term) < priority(f_term)
         THEN f_term || stack
         ELSE SUBSTR(stack, INSTR(stack, ' ') + 1)
         END,
         CASE
         WHEN f_term = '( '
         OR   (f_term = ') ' AND s_term = '( ')
         THEN output
         WHEN f_term = ') ' AND s_term != '( '
         THEN output || s_term
         WHEN is_operator(f_term) = 0
         THEN output || f_term
         WHEN stack IS NULL
         OR   s_term = '( '
         OR   priority(s_term) < priority(f_term)
         THEN output
         ELSE output || s_term
         END
  FROM   shunting_yard
         CROSS JOIN LATERAL (
           SELECT SUBSTR(input, 1, INSTR(input, ' '))
                    AS f_term,
                  SUBSTR(stack, 1, INSTR(stack, ' '))
                    AS s_term
           FROM   DUAL
         )
)
SEARCH DEPTH FIRST BY calc_cd, input SET order_id
CYCLE calc_cd, input, stack, output SET done TO 1 DEFAULT 0,
reverse_polish (unitnumber, calc_cd, formula, input, stack) AS (
  SELECT CAST(NULL AS VARCHAR2(100)),
         calc_cd,
         formula,
         output,
         CAST(NULL AS VARCHAR2(4000))
  FROM   shunting_yard
  WHERE  done = 1
UNION ALL
  SELECT t.unitnumber,
         r.calc_cd,
         r.formula,
         SUBSTR(r.input, INSTR(r.input, ' ', 1, 1) + 1),
         CASE t.f_term
         WHEN '+'
         THEN TO_CHAR(t.s_term2 + t.s_term1) || ' ' || t.s_tail
         WHEN '-'
         THEN TO_CHAR(t.s_term2 - t.s_term1) || ' ' || t.s_tail
         WHEN '*'
         THEN TO_CHAR(t.s_term2 * t.s_term1) || ' ' || t.s_tail
         WHEN '/'
         THEN TO_CHAR(t.s_term2 / t.s_term1) || ' ' || t.s_tail
         WHEN '^'
         THEN TO_CHAR(POWER(t.s_term2, t.s_term1)) || ' ' || t.s_tail
         ELSE COALESCE(TO_CHAR(t.value), t.f_term) || ' ' || r.stack
         END
  FROM   reverse_polish r
         CROSS JOIN LATERAL (
           SELECT SUBSTR(r.input, 1, INSTR(r.input, ' ') - 1) AS f_term,
                  TO_NUMBER(SUBSTR(r.stack, 1, INSTR(r.stack, ' ') - 1)) AS s_term1,
                  TO_NUMBER(
                    CASE 
                    WHEN INSTR(r.stack, ' ', 1, 2) > 0
                    THEN TO_NUMBER(
                           SUBSTR(
                             r.stack,
                             INSTR(r.stack, ' ', 1, 1) + 1,
                             INSTR(r.stack, ' ', 1, 2) - INSTR(r.stack, ' ', 1, 1) - 1
                           )
                         )
                    END
                  ) AS s_term2,
                  SUBSTR(r.stack, INSTR(r.stack, ' ', 1, 2) + 1) AS s_tail,
                  COALESCE(u.unitnumber, r.unitnumber) AS unitnumber,
                  u.grp_amt AS value
           FROM   DUAL
                  LEFT OUTER JOIN unitdata u
                  ON     (r.unitnumber IS NULL OR r.unitnumber = u.unitnumber)
                     AND r.calc_cd = u.calc_cd
                     AND SUBSTR(r.input, 1, INSTR(r.input, ' ') - 1) = u.grp_cd
         ) t
  WHERE  r.input IS NOT NULL
)
SEARCH DEPTH FIRST BY unitnumber, calc_cd, input SET order_id
CYCLE unitnumber, calc_cd, input, stack SET done TO 1 DEFAULT 0
SELECT unitnumber,
       calc_cd,
       formula,
       TO_NUMBER(stack) AS value
FROM   reverse_polish
WHERE  input IS NULL;

对于样本数据:

Create table Calc(
  CALC_CD  VARCHAR2(10),
  FORMULA1 VARCHAR2(200),
  FORMULA2 VARCHAR2(200),
  FORMULA3 VARCHAR2(200),
  FORMULA4 VARCHAR2(200)
);

Create table UnitData(
  UnitNumber VARCHAR2(100),
  CALC_CD    VARCHAR2(10),
  GRP_CD     VARCHAR2(100),
  GRP_AMT    NUMBER
);

insert into Calc (calc_cd, formula1, formula2, formula3, formula4)
  SELECT 'M10', 'SI01/VI01', 'SI02/VI02', 'Part1 * 0.3', 'Part2 + Part3' FROM DUAL UNION ALL
  SELECT 'M10', 'SI01', 'VI02', 'Part1', 'Part3 ^ Part2' FROM DUAL UNION ALL
  SELECT 'M10', '(SI01 - VI01)', '(SI02 + VI02)', '(Part1 * 0.3 + 2)', 'Part2 / Part3' FROM DUAL;

insert into UnitData (unitnumber, calc_cd, grp_cd, grp_amt)
  select '111E', 'M10', 'SI01', 233.23 from dual union
  select '111E', 'M10', 'VI01',   4.98 from dual union
  select '111E', 'M10', 'SI02', 233.23 from dual union
  select '111E', 'M10', 'VI02',   4.98 from dual union
  select '112E', 'M10', 'SI01', 133.23 from dual union
  select '112E', 'M10', 'VI01',   3.98 from dual union
  select '112E', 'M10', 'SI02', 133.23 from dual union
  select '112E', 'M10', 'VI02',   3.98 from dual

输出:

单位编号 CALC_CD 公式 价值
111E M10 SI01^VI02 618822894936.360378888509577862392629892
112E M10 SI01^VI02 285703835.699108676302209925319691931182
111E M10 (SI02 + VI02) / ((SI01 - VI01) * 0.3 + 2) 3.38006385242993969492727917701312522171
112E M10 (SI02 + VI02) / ((SI01 - VI01) * 0.3 + 2) 3.36505211526670754138565297363580625383
111E M10 SI02/VI02 + SI01/VI01 * 0.3 60.8833333333333333333333333333333333333
112E M10 SI02/VI02 + SI01/VI01 * 0.3 43.5173366834170854271356783919597989949

注意:

calc
表仅具有
calc_cd
列,并且与任何特定
unitdata
上的
unitnumber
无关,因此您将获得两个匹配的
unitnumber
的输出。

小提琴

© www.soinside.com 2019 - 2024. All rights reserved.