2010-03-19 16 views
6

Ho una tabella che descrive quali versioni del software sono stati installati su una macchina in tempi diversi:SQL vincolo CHECK per evitare sovrapposizioni data

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

mi piacerebbe fare un vincolo per garantire che non si sovrappongono gli intervalli di date , cioè non è possibile avere più versioni del software installate su una macchina contemporaneamente.

Come può essere ottenuto in SQL? Sto usando PostgreSQL v8.4.

risposta

12

in PostgreSQL 8.4 questo può essere risolto solo con i trigger. Il trigger dovrà verificare su inserimento/aggiornamento che non esistano righe in conflitto. Poiché la serializzabilità delle transazioni non implementa il blocco dei predicati, devi eseguire il blocco necessario da solo. Per fare ciò SELECT FOR UPDATE la riga nella tabella macchine in modo che nessun'altra transazione possa essere contemporaneamente l'inserimento di dati che potrebbero essere in conflitto.

In PostgreSQL 9.0 ci sarà una soluzione migliore, chiamata limitazioni di esclusione (in qualche modo documentata in CREATE TABLE). Ciò ti consente di specificare un vincolo che gli intervalli di date non devono sovrapporsi. Jeff Davis, l'autore di questa funzione ha un articolo in due parti su questo: part 1, part 2. Depesz ha anche qualche code examples describing the feature.

0

vuoi veramente un CONTROLLO vincolo e costrizione, come ho detto nel titolo? Ciò non è possibile, poiché i vincoli CHECK possono funzionare solo una riga alla volta. Ci potrebbe essere un modo per farlo utilizzando i trigger, anche se ...

+0

Qualsiasi modo di condizionare i dati sarà sufficiente. Ho appena (a torto!) Supposto che sarebbe stato un CONTROLLO ... – Michael

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

Nel frattempo (a partire dalla versione 9.2, se ho letto il manuale di corretta) PostgreSQL ha aggiunto il supporto per rangetypes.

Con queste rangetypes la questione diventa improvvisamente molto semplice (esempio copiato dal manuale):

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

E questo è tutto. Test (copiato anche dal manuale):

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")).

Problemi correlati