2010-03-19 5 views
6

J'ai une table qui décrit les versions logicielles ont été installées sur une machine à plusieurs reprises:SQL contrainte CHECK pour éviter le chevauchement de date

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

Je voudrais faire une contrainte pour garantir qu'aucune plages de dates se chevauchent , c'est-à-dire qu'il n'est pas possible d'avoir plusieurs versions logicielles installées sur une machine en même temps.

Comment cela peut-il être réalisé en SQL? J'utilise PostgreSQL v8.4.

Répondre

12

Dans PostgreSQL 8.4, ceci ne peut être résolu qu'avec des triggers. Le déclencheur devra vérifier sur insertion/mise à jour qu'il n'y a pas de lignes en conflit. La sérialisation des transactions n'impliquant pas le verrouillage des prédicats, vous devrez effectuer le verrouillage nécessaire par vous-même. Pour cela faire SELECT FOR UPDATE la ligne dans la table machines de sorte qu'aucune autre transaction ne puisse insérer simultanément des données qui pourraient entrer en conflit.

Dans PostgreSQL 9.0, il y aura une meilleure solution, appelée contraintes d'exclusion (quelque peu documentée sous CREATE TABLE). Cela vous permettra de spécifier une contrainte selon laquelle les plages de dates ne doivent pas se chevaucher. Jeff Davis, l'auteur de cette fonctionnalité a une écriture en deux parties à ce sujet: part 1, part 2. Depesz a aussi quelques code examples describing the feature.

0

Voulez-vous vraiment une costraint CHECK, comme mentionné dans le titre? Ce n'est pas possible, car les contraintes CHECK ne peuvent fonctionner qu'une seule ligne à la fois. Il pourrait y avoir un moyen de le faire en utilisant des triggers, mais ...

+0

Toute façon de contraindre les données seront suffisantes. J'ai juste (faussement!) Supposé que ce serait un CHECK ... – 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

En attendant (depuis la version 9.2 si je lis le manuel correctement) postgreSQL a ajouté le support pour rangetypes.

Avec ces rangetypes la question devient tout à coup très simple (par exemple copié à partir du manuel):

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

Et voilà. Test (également copié du manuel):

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

Questions connexes