2010-03-19 52 views
6

我有一个描述软件版本安装了哪些会在不同的机器上的表:SQL CHECK约束,以防止日期重叠

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

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

这怎么能在SQL中实现?我正在使用PostgreSQL v8.4。

回答

12

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

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

0

你真的想要检查costraint,就像标题中提到的一样吗?这是不可能的,因为CHECK约束一次只能工作一行。可能有办法使用触发器来做到这一点,尽管...

+0

约束的数据将是足够的任何方式。我只是(错误!)认为它将是一个CHECK ... – Michael 2010-03-19 11:46:41

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

同时(因为9.2如果我读正确的手动版本)PostgreSQL有添加rangetypes支持。

这个问题突然变得非常简单的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)'); 

ERROR: conflicting key value violates exclusion constraint "reservation_during_excl" DETAIL: Key (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00")) conflicts with existing key (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).