我在 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
让我们用 SQL 编写一个解析器...(将我的答案改编为类似的问题来处理不仅仅是单字符术语和文字术语)
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
的输出。