SQL CHECK约束以防止日期重叠

时间:2010-03-19 11:00:45

标签: sql postgresql constraints date-range

我有一张表格,描述了在不同时间在机器上安装了哪些软件版本:

machine_id::integer, version::text, datefrom::timestamp, dateto::timestamp

我想做一个限制,以确保没有日期范围重叠,即不可能同时在一台机器上安装多个软件版本。

如何在SQL中实现这一目标?我正在使用PostgreSQL v8.4。

6 个答案:

答案 0 :(得分:12)

在PostgreSQL 8.4中,这只能通过触发器来解决。触发器必须检查插入/更新是否存在冲突行。由于事务可序列化不实现谓词锁定,因此您必须自己进行必要的锁定。要在机器表中执行SELECT FOR UPDATE行,以便其他任何事务都不能同时插入可能冲突的数据。

在PostgreSQL 9.0中,会有一个更好的解决方案,称为排除约束(在CREATE TABLE下有所记录)。这将允许您指定日期范围不得重叠的约束。该功能的作者杰夫戴维斯对此进行了两部分的撰写:part 1part 2。 Depesz还有一些code examples describing the feature

答案 1 :(得分:8)

与此同时(从版本9.2开始,如果我正确阅读了手册),postgreSQL增加了对rangetypes的支持。

使用这些范围突然变得非常简单(例子从手册中复制):

CREATE TABLE reservation (
    during tsrange,
    EXCLUDE USING gist (during WITH &&)
);

就是这样。测试(也从手册中复制):

INSERT INTO reservation VALUES
    ('[2010-01-01 11:30, 2010-01-01 15:00)');
  

INSERT 0 1

INSERT INTO reservation VALUES
    ('[2010-01-01 14:45, 2010-01-01 15:45)');
  

错误:冲突的键值违反了排除约束“reservation_during_excl”       DETAIL:Key(during)=([“2010-01-01 14:45:00”,“2010-01-01 15:45:00”))冲突       使用现有密钥(期间)=([“2010-01-01 11:30:00”,“2010-01-01 15:00:00”))。

答案 2 :(得分:0)

你真的想要一个CHECK costraint,就像标题中提到的那样吗?这是不可能的,因为CHECK约束一次只能工作一行。可能有一种方法可以使用触发器,但是......

答案 3 :(得分:0)

-- Implementation of a CONSTRAINT on non-overlapping datetime ranges
-- , using the Postgres rulesystem.
-- This mechanism should work for 8.4, without needing triggers.(tested on 9.0)
-- We need a shadow-table for the rangesonly to avoid recursion in the rulesystem.
-- This shadow table has a canary variable with a CONSTRAINT (value=0) on it
-- , and on changes to the basetable (that overlap with an existing interval)
-- an attempt is made to modify this variable. (which of course fails)

-- CREATE SCHEMA tmp;
DROP table tmp.dates_shadow CASCADE;
CREATE table tmp.dates_shadow
    ( time_begin timestamp with time zone
    , time_end timestamp with time zone
    , overlap_canary INTEGER NOT NULL DEFAULT '0' CHECK (overlap_canary=0)
    );
ALTER table tmp.dates_shadow
    ADD PRIMARY KEY (time_begin,time_end)
    ;

DROP table tmp.dates CASCADE;
CREATE table tmp.dates
    ( time_begin timestamp with time zone
    , time_end timestamp with time zone
    , payload varchar
    );

ALTER table tmp.dates
    ADD PRIMARY KEY (time_begin,time_end)
    ;

CREATE RULE dates_i AS
    ON INSERT TO tmp.dates
    DO ALSO (
    -- verify shadow
    UPDATE tmp.dates_shadow ds
        SET overlap_canary= 1
        WHERE (ds.time_begin, ds.time_end)
           OVERLAPS ( NEW.time_begin, NEW.time_end)
        ;
    -- insert shadow
    INSERT INTO tmp.dates_shadow (time_begin,time_end)
        VALUES (NEW.time_begin, NEW.time_end)
        ;
    );

CREATE RULE dates_d AS
    ON DELETE TO tmp.dates
    DO ALSO (
    DELETE FROM tmp.dates_shadow ds
        WHERE ds.time_begin = OLD.time_begin
        AND ds.time_end = OLD.time_end
        ;
    );

CREATE RULE dates_u AS
    ON UPDATE TO tmp.dates
    WHERE NEW.time_begin <> OLD.time_begin
    AND NEW.time_end <> OLD.time_end
    DO ALSO (
    -- delete shadow
    DELETE FROM tmp.dates_shadow ds
        WHERE ds.time_begin = OLD.time_begin
        AND ds.time_end = OLD.time_end
        ;
    -- verify shadow
    UPDATE tmp.dates_shadow ds
        SET overlap_canary= 1
        WHERE (ds.time_begin, ds.time_end)
           OVERLAPS ( NEW.time_begin, NEW.time_end)
        ;
    -- insert shadow
    INSERT INTO tmp.dates_shadow (time_begin,time_end)
        VALUES (NEW.time_begin, NEW.time_end)
        ;
    );


INSERT INTO tmp.dates(time_begin,time_end) VALUES
  ('2011-09-01', '2011-09-10')
, ('2011-09-10', '2011-09-20')
, ('2011-09-20', '2011-09-30')
    ;
SELECT * FROM tmp.dates;

EXPLAIN ANALYZE
INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-30', '2011-10-04')
    ;

INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-02', '2011-09-04')
    ;

SELECT * FROM tmp.dates;
SELECT * FROM tmp.dates_shadow;

答案 4 :(得分:0)

如果出于某种原因您不能更改表架构并且需要保留两个时间行,则可以在约束内构建范围,例如:

CREATE TABLE reservations (
    datefrom timestamp,
    dateto timestamp,
    EXCLUDE USING gist (tsrange(datefrom, dateto) WITH &&)
);

在这种情况下,我使用tsrange处理时间戳类型,但是您还可以使用其他类型-请查看https://www.postgresql.org/docs/current/rangetypes.html上的文档。

答案 5 :(得分:0)

CREATE EXTENSION IF NOT EXISTS btree_gist; 

CREATE TABLE machines(
    machine_id integer, 
    version text, 
    during tsrange,
    EXCLUDE USING gist ( machine_id with =, during with &&)
);

表中id相同的机器不会重叠。