2

我被困在一个小要求中。如果插入或更新任何重叠数据,我的表应该限制。

以下是我到目前为止的尝试:

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。我正面临变异表错误。请在这里帮助我。

表数据

提前致谢。

4

2 回答 2

2

正如@mic.sca 的回答所说,触发器是实施此类规则的一种糟糕/棘手的方式。你真正想要的是一个可以在表级而不是行级工作的约束。ANSI SQL 将其称为“断言”,但迄今为止还没有 DBMS 供应商实现这一点(尽管 Oracle 似乎正在认真考虑在未来的版本中这样做)。

但是,有一种方法可以使用物化视图来模拟这种约束/断言。我早在 2004 年就以这种方式写过博客——您的要求与我的示例 2 非常相似。为您的表修改,这将是:

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 语法创建物化视图,但可以使用等效的旧式语法!

于 2018-02-04T11:59:20.663 回答
1

发生变异表错误是因为在触发器中的更新期间您选择了要更新的同一行。

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

防止对同一个id进行并发操作。您还需要有一种机制来序列化运行数据操作的可能并发会话。您可能有一个带有您的 id 的单独父表,并且对特定 Id 进行操作的所有操作都应该在对 my_table 运行插入或更新之前在父表上对该 id 执行选择以更新。

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

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

于 2018-02-04T09:27:51.307 回答