Ограничение SQL CHECK для предотвращения наложения даты - PullRequest
6 голосов
/ 19 марта 2010

У меня есть таблица, которая описывает, какие версии программного обеспечения были установлены на машине в разное время:

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

Я хотел бы сделать ограничение, чтобы гарантировать, что никакие диапазоны дат не перекрываются, т. Е. Невозможно установить несколько версий программного обеспечения на машину одновременно.

Как этого можно достичь в SQL? Я использую PostgreSQL v8.4.

Ответы [ 4 ]

12 голосов
/ 19 марта 2010

В PostgreSQL 8.4 это можно решить только с помощью триггеров. Триггер должен будет проверить при вставке / обновлении, что не существует конфликтующих строк. Поскольку сериализуемость транзакций не реализует блокировку предикатов, вам придется выполнять необходимую блокировку самостоятельно. Для этого SELECT FOR UPDATE строка в таблице компьютеров, чтобы никакая другая транзакция не могла одновременно вставлять данные, которые могут конфликтовать.

В PostgreSQL 9.0 будет лучшее решение для этого, называемое исключающими ограничениями (несколько документировано под CREATE TABLE ) Это позволит вам указать ограничение, которое диапазоны дат не должны перекрывать. У Джеффа Дэвиса, автора этой функции, есть две части: часть 1 , часть 2 . У Depesz также есть несколько примеров кода, описывающих функцию .

8 голосов
/ 12 марта 2015

Тем временем (начиная с версии 9.2, если я правильно прочитал руководство), в postgreSQL добавлена ​​поддержка 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)');

ОШИБКА: конфликтующее значение ключа нарушает ограничение исключения "servation_during_excl " ДЕТАЛИ: Ключевые (в процессе) = (["2010-01-01 14:45:00", "2010-01-01 15:45:00")) конфликты с существующим ключом (во время) = (["2010-01-01 11:30:00", "2010-01-01 15:00:00")).

0 голосов
/ 22 октября 2011
-- 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;
0 голосов
/ 19 марта 2010

Вы действительно хотите узнать цену CHECK, как указано в названии?Это невозможно, поскольку ограничения CHECK могут работать только по одной строке за раз.Возможно, есть способ сделать это с помощью триггеров, но ...

...