2010-03-19 12 views
6

Tengo una tabla que describe qué versiones de software se han instalado en una máquina en varios momentos:SQL restricción CHECK para evitar la superposición fecha

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

me gustaría hacer una restricción para asegurar que no se superponen los intervalos de fechas , es decir, no es posible tener múltiples versiones de software instaladas en una máquina al mismo tiempo.

¿Cómo se puede lograr esto en SQL? Estoy usando PostgreSQL v8.4.

Respuesta

12

En PostgreSQL 8.4 esto solo se puede resolver con disparadores. El desencadenador deberá verificar insert/update que no existen filas conflictivas. Debido a que la serializabilidad de la transacción no implementa el bloqueo de predicados, usted tendrá que realizar el bloqueo necesario por su cuenta. Para hacer eso, SELECT FOR UPDATE la fila en la tabla de máquinas para que ninguna otra transacción pueda insertar simultáneamente datos que puedan entrar en conflicto.

En PostgreSQL 9.0 habrá una solución mejor a esto, llamada restricciones de exclusión (algo documentado bajo CREATE TABLE). Eso le permitirá especificar una restricción que los rangos de fechas no se superpongan. Jeff Davis, el autor de esa característica, tiene una reseña en dos partes sobre esto: part 1, part 2. Depesz también tiene algunos code examples describing the feature.

0

¿De verdad quieres un COSTRANTE CHECK, como se menciona en el título? Eso no es posible, ya que las restricciones CHECK solo pueden trabajar una fila a la vez. Sin embargo, podría haber una forma de hacer esto usando desencadenadores ...

+0

Cualquier manera de limitar los datos será suficiente. Solo (¡erróneamente!) Supuse que sería 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

Mientras tanto (desde la versión 9.2 si leo el manual correctamente) PostgreSQL ha añadido soporte para rangetypes.

Con esas rangetypes el tema de repente se vuelve muy simple (ejemplo copiado del manual):

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

Y eso es todo. Test (también copiado del manual):

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

Cuestiones relacionadas