You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
/

关键注意事项

  1. 运算符优先级严格遵循NOT > AND > OR,同优先级按左结合处理。
  2. 临时表使用全局临时表,确保会话隔离,避免不同请求的计算结果互相干扰。
  3. 处理区间运算时,注意过滤无效区间(开始时间晚于结束时间),并合并重叠/连续区间。
  4. 表达式中的规则ID需为数字,临时规则用前缀T标识,避免与正式规则ID冲突。

内容的提问来源于stack exchange,提问作者Narasimhan M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 13:52:15