2012-05-16 3 views
16

У меня есть таблица вроде этого:дата Postgres перекрывание ограничение

date_start date_end  account_id product_id 
2001-01-01 2001-01-31 1    1 
2001-02-01 2001-02-20 1    1 
2001-04-01 2001-05-20 1    1 

Я хочу, чтобы запретить перекрывающихся интервалах заданной (account_id, product_id)

EDIT: Я нашел кое-что:

CREATE TABLE test (                        
    from_ts TIMESTAMPTZ, 
    to_ts TIMESTAMPTZ, 
    account_id INTEGER, 
    product_id INTEGER, 
    CHECK (from_ts < to_ts), 
    CONSTRAINT overlapping_times EXCLUDE USING GIST (
     account_id WITH =, 
     product_id WITH =, 
     box(
      point(extract(epoch FROM from_ts at time zone 'UTC'), extract(epoch FROM from_ts at time zone 'UTC')), 
      point(extract(epoch FROM to_ts at time zone 'UTC') , extract(epoch FROM to_ts at time zone 'UTC')) 
     ) WITH && 
    ) 
); 

Если вы хотите чтобы узнать больше об этом http://www.depesz.com/2010/01/03/waiting-for-8-5-exclusion-constraints/

Только для меня проблема в том, что она не работает с нулевыми значениями как конечная временная метка, я думал о ее замене бесконечными значениями, но не работает.

+2

Вы хотите запретить перекрывающиеся интервалы? – wildplasser

+0

yep точно это слово, которое я искал – yokoloko

+0

Вы должны пойти для 'CREATE CONSTRAINT TRIGGER' и написать определенную функцию для выполнения вашей проверки. – vyegorov

ответ

22

Хорошо я в конечном итоге делает это:

CREATE TABLE test (
    from_ts TIMESTAMPTZ, 
    to_ts TIMESTAMPTZ, 
    account_id INTEGER DEFAULT 1, 
    product_id INTEGER DEFAULT 1, 
    CHECK (from_ts < to_ts), 
    CONSTRAINT overlapping_times EXCLUDE USING GIST (
     account_id WITH =, 
     product_id WITH =, 
     period(from_ts, CASE WHEN to_ts IS NULL THEN 'infinity' ELSE to_ts END) WITH && 
    ) 
); 

Работает отлично с бесконечностью, доказательство транзакции.

Я просто должен был установить временное расширение, которое собирается быть родным в Postgres 9.2 и btree_gist доступных как расширение в 9,1 CREATE EXTENSION btree_gist;

нб: если вы не имеете нулевую отметку времени нет необходимости использовать временное расширение, которое вы можете использовать с методом box, как указано в моем вопросе.

+0

+1 Идеальный вариант использования для [ограничения исключения] (http://www.postgresql.org/docs/9.1/static/ddl-constraints.html#DDL-CONSTRAINTS-EXCLUSION) (новый в Postgres 9.0). Вместо выражения 'CASE' вы можете определить столбец' to_ts TIMESTAMPTZ NOT NULL DEFAULT 'infinity''. И то же самое с '' -infinity'' для 'from_ts'. –

+0

Я думал об этом бесконечно как DEFAULT, но потом он меняет поведение. Я действительно не знаю, какое влияние это повлияет на мой код, я никогда не работал с timestamp бесконечности. Хотя в этом случае, вероятно, больше смысла использовать бесконечность. – yokoloko

+2

С выпуском 9.2 был добавлен тип диапазона, включая daterange, который упрощает вашу константу и весь этот проблемный домен. http://www.postgresql.org/docs/devel/static/rangetypes.html – toxaq

-3

Как создать уникальное ограничение для группы столбцов:

CREATE TABLE table (
    date_start date, 
    date_end date, 
    account_id integer, 
    UNIQUE (account_id , date_start ,date_end)); 

в вашем случае вам нужно будет ALTER TABLE, если таблица уже существует, то проверьте документацию было бы полезно для вас:
- DDL Constraints
- ALTER Table

+0

pg-8.1 - довольно старая версия. – wildplasser

+0

Хмм это не то, что я ищу. Я ищу что-то, чтобы запретить пересекающиеся интервалы. но спасибо – yokoloko

0

Это трудная проблема, потому что constraints can only reference the "current row", и не может содержать подзапросы. (В противном случае тривиальное решение было бы добавить некоторые NOT EXISTS() подзапрос в чеке)

Проверочное ограничение определяется как ограничение столбца следует опорное значение этого столбца только, в то время как выражение появляется в таблице ограничений может ссылаться на несколько столбцов.

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

Популярных обходные являются: использовать функцию триггера, которая делает грязную работу (или использовать систему правил, которая устаревшее большинство людей)

Поскольку большинство людей выступают триггера, я буду перепечатывать правило-система взломать здесь ... (это не имеет дополнительный «идентификатор» ключевой элемент, но это мелочь)

-- Implementation of A CONSTRAINT on non-overlapping datetime ranges 
-- , using the Postgres rulesystem. 
-- We need a shadow-table for the ranges only 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; 
+1

Я думаю, что можно что-то сделать с исключением ограничений. Не уверен, хотя, взгляните на мое редактирование. – yokoloko

0

В современных версиях postgres (я тестировал его в 9.6, но я предполагаю, что он работает в> = 9.2), вы можете использовать функцию построения tstzrange(), как упоминалось в некоторых других комментариях. Значения Null будут считаться положительной или отрицательной бесконечностью по умолчанию, и больше не требуется явно запрет CHECK (если вы в порядке, чтобы проверка была только <=, и диапазон может начинаться и заканчиваться с той же датой).Необходимо только расширение btree_gist:

CREATE EXTENSION btree_gist; 

CREATE TABLE test (
    from_ts TIMESTAMPTZ, 
    to_ts TIMESTAMPTZ, 
    account_id INTEGER DEFAULT 1, 
    product_id INTEGER DEFAULT 1, 
    CONSTRAINT overlapping_times EXCLUDE USING GIST (
     account_id WITH =, 
     product_id WITH =, 
     TSTZRANGE(from_ts, to_ts) WITH && 
    ) 
); 
Смежные вопросы