Oracle mutating trigger 解决方案

Solution to Oracle mutating trigger

我被一个小需求卡住了。 我的 table 应该限制是否插入或更新任何重叠数据。

以下是我目前的尝试:

CREATE TABLE my_table (
    ID          NUMBER,
    startdate   DATE,
    enddate     DATE,
    CONSTRAINT my_table_pk PRIMARY KEY ( ID,startdate,enddate )
);
/
CREATE OR REPLACE TRIGGER trg_my_table_biu
    BEFORE INSERT OR UPDATE 
    ON my_table
    FOR EACH ROW
DECLARE 
    v_count NUMBER;
BEGIN

    SELECT COUNT(*)
    INTO v_count
    FROM my_table
    WHERE  id = :new.id
    AND startdate < = :new.enddate
    AND enddate   >= :new.startdate;

    IF v_count >= 1 THEN
        raise_application_error( -20001, 'Cannot make the data overlapped.!' );
    END IF;
END;

/
--existing data - good data - Result: Success

INSERT INTO my_table VALUES (1, to_date('01/02/2018','dd/mm/yyyy '),to_date('01/03/2018','dd/mm/yyyy '));

--1 good data - Result: Success
INSERT INTO my_table VALUES (1, to_date('01/01/2018','dd/mm/yyyy '),to_date('15/01/2018','dd/mm/yyyy '));   

--2 good data - Result: Success
INSERT INTO my_table VALUES (1, to_date('02/03/2018','dd/mm/yyyy '),to_date('31/03/2018','dd/mm/yyyy '));

--3 bad data - Result: Success
INSERT INTO MY_TABLE VALUES (1, TO_DATE('01/01/2018','dd/mm/yyyy '),TO_DATE('01/04/2018','dd/mm/yyyy '));

--4 bad data - Result: Success
INSERT INTO my_table VALUES (1, to_date('15/01/2018','dd/mm/yyyy '),to_date('02/02/2018','dd/mm/yyyy '));

--5 bad data - Result: Success
INSERT INTO my_table VALUES (1, to_date('16/02/2018','dd/mm/yyyy '),to_date('15/03/2018','dd/mm/yyyy '));

--6 bad data - Result: Success
INSERT INTO my_table VALUES (1, to_date('15/02/2018','dd/mm/yyyy '),to_date('20/02/2018','dd/mm/yyyy '));

--7 good data - Result: Fail
UPDATE my_table 
SET enddate = TO_DATE('31/03/2018','dd/mm/yyyy') + 1 
WHERE startdate = TO_DATE('02/03/2018','dd/mm/yyyy');

对于第 7 个语句,即 UPDATE。我面临 mutaing table 错误。 请在这里帮助我。

提前致谢。

发生变异 table 错误是因为在触发器更新期间,您 select 正在更新同一行。

我的建议是不要使用触发器,而是使用存储过程进行所有插入和更新,这些存储过程在执行操作之前检查日期是否重叠。

防止对同一个id进行并发操作。您还需要有一种机制来序列化可能的并发会话 运行 对数据的操作。您可能有一个单独的父级 table 和您的 ID,所有对特定 ID 进行操作的操作都应该在 [=27= 之前对父级 table 上的该 ID 进行 select 更新]ning 在 my_table.

上插入或更新

触发器可能看起来很酷,但在长期 运行 中可能会造成维护问题,因为它们不是那么明确,并且它们适用于 table(http://www.oracle.com/technetwork/testcontent/o58asktom-101055.html) 上的所有操作。

顺便说一句,如果两个用户使用您的触发器同时更新具有相同 ID 的两行,您最终可能会得到重叠的值,而您的触发器不会引发任何错误(尽管这种可能性很小)。

正如@mic.sca 的回答所说,触发器是 poor/tricky 实现此类规则的方式。您真正想要的是可以在 table 级别而不是行级别工作的约束。 ANSI SQL 将其称为 "assertion",但迄今为止还没有 DBMS 供应商实现这一点(尽管似乎 Oracle is seriously considering doing so in a future release)。

但是,有一种方法可以使用实体化视图来模拟这种 constraint/assertion。 I blogged about this way back in 2004 - 您的要求与我的示例 2 非常相似。针对您的 table 修改为:

create materialized view my_table_mv1
refresh complete on commit as
select 1 dummy
from my_table t1, my_table t2
where t1.id = t2.id
and t1.startdate <= t2.enddate
and t1.enddate >= t2.startdate;

alter table my_table_mv1
add constraint my_table_mv1_chk
check (1=0) deferrable;

此实体化视图仅包含重叠实例,因此应始终为空。一旦创建了重叠,一行就被插入到物化视图中——但立即违反了它的检查约束,这是永远无法满足的!

请注意,这是一个延迟约束,即直到提交时才会对其进行检查。

顺便说一句,我不知道为什么我在 2004 年没有使用 ANSI 连接语法 - 也许我当时没有使用它。但是,在某些情况下(我认为更多的是外连接)无法使用 ANSI 语法创建物化视图,但可以使用等效的旧式语法!