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 语法创建物化视图,但可以使用等效的旧式语法!
我被一个小需求卡住了。 我的 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 语法创建物化视图,但可以使用等效的旧式语法!