Oracle中基于拆分变量与括号的BODMAS表达式求值实现
在Oracle中实现带括号优先级的日期区间逻辑表达式求值
核心思路
要支持遵循括号优先级的动态表达式求值,核心是递归解析+逐步替换:先定位最内层括号,计算其结果并替换成临时虚拟规则,重复此过程直到表达式无括号,最后按运算符优先级(NOT > AND > OR)计算顶层表达式。
步骤1:定义基础数据结构与区间运算函数
首先定义存储日期区间的集合类型,以及处理两两区间运算的基础函数:
-- 定义单个日期区间类型 CREATE OR REPLACE TYPE date_interval IS RECORD ( active_from DATE, active_to DATE ); / -- 定义区间列表类型 CREATE OR REPLACE TYPE interval_list IS TABLE OF date_interval; / -- 创建全局临时表存储临时计算结果(会话隔离) CREATE GLOBAL TEMPORARY TABLE temp_rule_intervals ( temp_rule_id NUMBER, active_from DATE, active_to DATE ) ON COMMIT DELETE ROWS; / -- 序列生成临时规则ID CREATE SEQUENCE temp_rule_seq START WITH 1 INCREMENT BY 1; / -- 区间交集(AND)运算 CREATE OR REPLACE FUNCTION calc_interval_and( p_int1 interval_list, p_int2 interval_list ) RETURN interval_list IS v_result interval_list := interval_list(); BEGIN FOR i IN p_int1.FIRST .. p_int1.LAST LOOP FOR j IN p_int2.FIRST .. p_int2.LAST LOOP v_result.EXTEND(); v_result(v_result.LAST).active_from := GREATEST(p_int1(i).active_from, p_int2(j).active_from); v_result(v_result.LAST).active_to := LEAST(p_int1(i).active_to, p_int2(j).active_to); -- 过滤无效区间(开始时间晚于结束时间) IF v_result(v_result.LAST).active_from > v_result(v_result.LAST).active_to THEN v_result.TRIM(); END IF; END LOOP; END LOOP; RETURN v_result; END; / -- 区间并集(OR)运算 CREATE OR REPLACE FUNCTION calc_interval_or( p_int1 interval_list, p_int2 interval_list ) RETURN interval_list IS v_combined interval_list := interval_list(); v_sorted interval_list := interval_list(); v_result interval_list := interval_list(); BEGIN -- 合并两个区间列表 IF p_int1 IS NOT NULL THEN v_combined := p_int1; END IF; IF p_int2 IS NOT NULL THEN FOR i IN p_int2.FIRST .. p_int2.LAST LOOP v_combined.EXTEND(); v_combined(v_combined.LAST) := p_int2(i); END LOOP; END IF; -- 按开始时间排序 SELECT VALUE(t) BULK COLLECT INTO v_sorted FROM TABLE(v_combined) t ORDER BY t.active_from; -- 合并重叠/连续区间 IF v_sorted.COUNT > 0 THEN v_result.EXTEND(); v_result(1) := v_sorted(1); FOR i IN 2 .. v_sorted.COUNT LOOP IF v_sorted(i).active_from <= v_result(v_result.LAST).active_to + 1 THEN -- 重叠或连续,合并区间 v_result(v_result.LAST).active_to := GREATEST(v_result(v_result.LAST).active_to, v_sorted(i).active_to); ELSE -- 无重叠,新增区间 v_result.EXTEND(); v_result(v_result.LAST) := v_sorted(i); END IF; END LOOP; END IF; RETURN v_result; END; / -- 区间差集(NOT)运算:返回p_int1中不在p_int2的部分 CREATE OR REPLACE FUNCTION calc_interval_not( p_int1 interval_list, p_int2 interval_list ) RETURN interval_list IS v_result interval_list := interval_list(); v_temp interval_list := interval_list(); BEGIN IF p_int2 IS NULL OR p_int2.COUNT = 0 THEN RETURN p_int1; END IF; FOR i IN p_int1.FIRST .. p_int1.LAST LOOP v_temp := interval_list(); v_temp.EXTEND(); v_temp(1) := p_int1(i); -- 用p_int2的每个区间切割当前区间 FOR j IN p_int2.FIRST .. p_int2.LAST LOOP DECLARE v_current date_interval := v_temp(1); BEGIN v_temp.TRIM(); -- 情况1:当前区间完全在切割区间左侧 IF v_current.active_to < p_int2(j).active_from THEN v_temp.EXTEND(); v_temp(v_temp.LAST) := v_current; -- 情况2:当前区间完全在切割区间右侧 ELSIF v_current.active_from > p_int2(j).active_to THEN v_temp.EXTEND(); v_temp(v_temp.LAST) := v_current; -- 情况3:切割区间完全包含当前区间 ELSIF v_current.active_from >= p_int2(j).active_from AND v_current.active_to <= p_int2(j).active_to THEN NULL; -- 区间被完全移除 -- 情况4:当前区间左侧部分未被覆盖 ELSIF v_current.active_from < p_int2(j).active_from THEN v_temp.EXTEND(); v_temp(v_temp.LAST).active_from := v_current.active_from; v_temp(v_temp.LAST).active_to := p_int2(j).active_from - 1; -- 剩余右侧部分继续处理 IF v_current.active_to > p_int2(j).active_to THEN v_temp.EXTEND(); v_temp(v_temp.LAST).active_from := p_int2(j).active_to + 1; v_temp(v_temp.LAST).active_to := v_current.active_to; END IF; -- 情况5:当前区间右侧部分未被覆盖 ELSIF v_current.active_to > p_int2(j).active_to THEN v_temp.EXTEND(); v_temp(v_temp.LAST).active_from := p_int2(j).active_to + 1; v_temp(v_temp.LAST).active_to := v_current.active_to; END IF; END; END LOOP; -- 将切割后的有效区间加入结果 FOR k IN v_temp.FIRST .. v_temp.LAST LOOP v_result.EXTEND(); v_result(v_result.LAST) := v_temp(k); END LOOP; END LOOP; RETURN v_result; END; /
步骤2:实现递归表达式解析函数
编写递归函数,处理括号的层级替换,直到表达式扁平化,再按优先级计算:
-- 从规则表或临时表获取区间列表 CREATE OR REPLACE FUNCTION get_rule_intervals( p_rule_id VARCHAR2 ) RETURN interval_list IS v_result interval_list := interval_list(); BEGIN -- 判断是正式规则ID还是临时规则ID(前缀T) IF SUBSTR(p_rule_id, 1, 1) = 'T' THEN SELECT date_interval(active_from, active_to) BULK COLLECT INTO v_result FROM temp_rule_intervals WHERE temp_rule_id = TO_NUMBER(SUBSTR(p_rule_id, 2)); ELSE SELECT date_interval(active_from, active_to) BULK COLLECT INTO v_result FROM your_rule_table -- 替换为你的规则表名 WHERE rule_id = TO_NUMBER(p_rule_id); END IF; RETURN v_result; END; / -- 处理扁平化表达式(无括号) CREATE OR REPLACE FUNCTION evaluate_flat_expression( p_expr VARCHAR2 ) RETURN interval_list IS v_tokens VARCHAR2(4000) := p_expr; v_operator VARCHAR2(3); v_left_rule VARCHAR2(20); v_right_rule VARCHAR2(20); v_left_intervals interval_list; v_right_intervals interval_list; v_temp_id NUMBER; BEGIN -- 先处理所有NOT运算(最高优先级) WHILE INSTR(v_tokens, ' NOT ') > 0 LOOP v_operator := 'NOT'; v_right_rule := TRIM(SUBSTR(v_tokens, INSTR(v_tokens, ' NOT ') + 4)); -- 提取右侧规则(直到下一个运算符或结束) IF INSTR(v_right_rule, ' ') > 0 THEN v_right_rule := SUBSTR(v_right_rule, 1, INSTR(v_right_rule, ' ') - 1); END IF; v_left_rule := TRIM(SUBSTR(v_tokens, 1, INSTR(v_tokens, ' NOT ') - 1)); v_left_intervals := get_rule_intervals(v_left_rule); v_right_intervals := get_rule_intervals(v_right_rule); v_left_intervals := calc_interval_not(v_left_intervals, v_right_intervals); -- 生成临时规则替换原表达式片段 SELECT temp_rule_seq.NEXTVAL INTO v_temp_id FROM DUAL; FOR i IN v_left_intervals.FIRST .. v_left_intervals.LAST LOOP INSERT INTO temp_rule_intervals(temp_rule_id, active_from, active_to) VALUES(v_temp_id, v_left_intervals(i).active_from, v_left_intervals(i).active_to); END LOOP; v_tokens := REPLACE(v_tokens, v_left_rule || ' NOT ' || v_right_rule, 'T' || v_temp_id); END LOOP; -- 处理所有AND运算(次优先级) WHILE INSTR(v_tokens, ' AND ') > 0 LOOP v_operator := 'AND'; v_left_rule := TRIM(SUBSTR(v_tokens, 1, INSTR(v_tokens, ' AND ') - 1)); v_right_rule := TRIM(SUBSTR(v_tokens, INSTR(v_tokens, ' AND ') + 4)); IF INSTR(v_right_rule, ' ') > 0 THEN v_right_rule := SUBSTR(v_right_rule, 1, INSTR(v_right_rule, ' ') - 1); END IF; v_left_intervals := get_rule_intervals(v_left_rule); v_right_intervals := get_rule_intervals(v_right_rule); v_left_intervals := calc_interval_and(v_left_intervals, v_right_intervals); SELECT temp_rule_seq.NEXTVAL INTO v_temp_id FROM DUAL; FOR i IN v_left_intervals.FIRST .. v_left_intervals.LAST LOOP INSERT INTO temp_rule_intervals(temp_rule_id, active_from, active_to) VALUES(v_temp_id, v_left_intervals(i).active_from, v_left_intervals(i).active_to); END LOOP; v_tokens := REPLACE(v_tokens, v_left_rule || ' AND ' || v_right_rule, 'T' || v_temp_id); END LOOP; -- 处理所有OR运算(最低优先级) WHILE INSTR(v_tokens, ' OR ') > 0 LOOP v_operator := 'OR'; v_left_rule := TRIM(SUBSTR(v_tokens, 1, INSTR(v_tokens, ' OR ') - 1)); v_right_rule := TRIM(SUBSTR(v_tokens, INSTR(v_tokens, ' OR ') + 3)); IF INSTR(v_right_rule, ' ') > 0 THEN v_right_rule := SUBSTR(v_right_rule, 1, INSTR(v_right_rule, ' ') - 1); END IF; v_left_intervals := get_rule_intervals(v_left_rule); v_right_intervals := get_rule_intervals(v_right_rule); v_left_intervals := calc_interval_or(v_left_intervals, v_right_intervals); SELECT temp_rule_seq.NEXTVAL INTO v_temp_id FROM DUAL; FOR i IN v_left_intervals.FIRST .. v_left_intervals.LAST LOOP INSERT INTO temp_rule_intervals(temp_rule_id, active_from, active_to) VALUES(v_temp_id, v_left_intervals(i).active_from, v_left_intervals(i).active_to); END LOOP; v_tokens := REPLACE(v_tokens, v_left_rule || ' OR ' || v_right_rule, 'T' || v_temp_id); END LOOP; -- 返回最终结果 RETURN get_rule_intervals(v_tokens); END; / -- 递归解析带括号的表达式 CREATE OR REPLACE FUNCTION evaluate_expression( p_expr VARCHAR2 ) RETURN interval_list IS v_expr VARCHAR2(4000) := p_expr; v_left_paren NUMBER; v_right_paren NUMBER; v_sub_expr VARCHAR2(4000); v_temp_id NUMBER; v_sub_result interval_list; BEGIN -- 查找最内层括号 v_right_paren := INSTR(v_expr, ')'); WHILE v_right_paren > 0 LOOP v_left_paren := INSTR(v_expr, '(', -1, 1, v_right_paren); IF v_left_paren = 0 THEN RAISE_APPLICATION_ERROR(-20001, '表达式括号不匹配'); END IF; -- 提取括号内的子表达式并计算 v_sub_expr := TRIM(SUBSTR(v_expr, v_left_paren + 1, v_right_paren - v_left_paren - 1)); v_sub_result := evaluate_expression(v_sub_expr); -- 生成临时规则存储子表达式结果 SELECT temp_rule_seq.NEXTVAL INTO v_temp_id FROM DUAL; FOR i IN v_sub_result.FIRST .. v_sub_result.LAST LOOP INSERT INTO temp_rule_intervals(temp_rule_id, active_from, active_to) VALUES(v_temp_id, v_sub_result(i).active_from, v_sub_result(i).active_to); END LOOP; -- 替换原表达式中的括号部分为临时规则ID v_expr := SUBSTR(v_expr, 1, v_left_paren - 1) || 'T' || v_temp_id || SUBSTR(v_expr, v_right_paren + 1); v_right_paren := INSTR(v_expr, ')'); END LOOP; -- 处理扁平化后的表达式 RETURN evaluate_flat_expression(v_expr); END; /
使用示例
假设你的规则表名为item_rules,调用方式如下:
DECLARE v_result interval_list; BEGIN -- 计算表达式:(R1 AND R2) OR (NOT R3) v_result := evaluate_expression('(1 AND 2) OR (NOT 3)'); -- 输出结果区间 FOR i IN v_result.FIRST .. v_result.LAST LOOP DBMS_OUTPUT.PUT_LINE('区间:' || v_result(i).active_from || ' 至 ' || v_result(i).active_to); END LOOP; END; /
关键注意事项
- 运算符优先级严格遵循
NOT > AND > OR,同优先级按左结合处理。 - 临时表使用全局临时表,确保会话隔离,避免不同请求的计算结果互相干扰。
- 处理区间运算时,注意过滤无效区间(开始时间晚于结束时间),并合并重叠/连续区间。
- 表达式中的规则ID需为数字,临时规则用前缀
T标识,避免与正式规则ID冲突。
内容的提问来源于stack exchange,提问作者Narasimhan M
相关产品推荐
相关产品推荐

