2010-03-19 54 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")).