2012-05-16 16 views
16

Mam tabeli tak:Postgres data nakładających ograniczenia

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 

Chcę, aby uniemożliwić nakładających przedziały daną (account_id, product_id)

EDIT: Znalazłem coś:

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 && 
    ) 
); 

Jeśli chcesz aby dowiedzieć się więcej na ten temat: http://www.depesz.com/2010/01/03/waiting-for-8-5-exclusion-constraints/

Moje jedyne Problem polega na tym, że nie działa z wartościami null jako końcowym znacznikiem czasu, myślałem o zastąpieniu go wartościami nieskończonymi, ale nie działa tak dobrze.

+2

Chcesz zabronić nakładania się interwałów? – wildplasser

+0

tak dokładnie to jest słowo, którego szukałem – yokoloko

+0

Powinieneś pójść na 'CREATE CONSTRAINT TRIGGER' i napisać określoną funkcję, aby wykonać czek. – vyegorov

Odpowiedz

22

Ok skończyło się w ten sposób:

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 && 
    ) 
); 

Działa idealnie w nieskończoności, transakcja dowód.

po prostu musiałem zainstalować rozszerzenie czasową, która będzie rodzimy w PostgreSQL 9.2 i btree_gist dostępnych jako przedłużenie w 9,1 CREATE EXTENSION btree_gist;

UWAGA: Jeżeli nie masz zerowej znacznik czasu nie ma potrzeby używać przedłużenie czasowe można przejść za pomocą metody skrzynki określonej w moim pytaniu.

+0

+1 Idealny przypadek użycia dla [ograniczenia wykluczenia] (http://www.postgresql.org/docs/9.1/static/ddl-constraints.html#DDL-CONSTRAINTS-EXCLUSION) (nowy w Postgres 9.0). Zamiast instrukcji 'CASE' możesz zdefiniować kolumnę' to_ts TIMESTAMPTZ NOT NULL DEFAULT 'infinity''. I to samo z '' -infinity'' dla 'from_ts'. –

+0

Myślałem o tym dla nieskończoności jako DEFAULT, ale potem zmienia to zachowanie. Naprawdę nie wiem, jaki wpływ będzie miało na mój kod, nigdy nie pracowałem z sygnaturą nieskończoności. Nawet jeśli w tym przypadku bardziej prawdopodobne jest używanie nieskończoności. – yokoloko

+2

W wydaniu 9.2 dodano typ zakresu, w tym daterange, który upraszcza twoją postać i tę całą domenę problemową. http://www.postgresql.org/docs/devel/static/rangetypes.html – toxaq

-3

Jak stworzyć unikalną ograniczenie do grupy kolumn:

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

w twoim przypadku trzeba będzie ALTER TABLE jeśli tabela już istnieje, sprawdź dokumentację byłoby pomocne dla Ciebie:
- DDL Constraints
- ALTER Table

+0

pg-8.1 to dość stara wersja. – wildplasser

+0

hmm, to naprawdę nie jest to, czego szukam. Szukam czegoś, co uniemożliwi nakładanie się interwałów. ale dzięki – yokoloko

0

to trudny problem, bo constraints can only reference the "current row" i nie mogą zawierać podzapytania. (Inaczej trywialne rozwiązanie byłoby dodać trochę NOT EXISTS() podzapytania w czeku)

Ograniczenie sprawdzające określone jako ograniczenie kolumny powinny odwoływać się tylko wartość tej kolumny, a wyrazem pojawiające się ograniczenie tabeli może odwoływać się wiele kolumn.

Obecnie wyrażenia CHECK nie mogą zawierać podzapytań ani odwoływać się do zmiennych innych niż kolumny bieżącego wiersza.

Popularne obejścia są: użyj funkcji wyzwalania który robi brudną robotę (lub korzystać z systemu reguł, która jest nieaktualna od większości ludzi)

Ponieważ większość ludzi sprzyjać wyzwalacze, będę ponownej publikacji zasada System włamać tutaj ... (nie ma dodatkowych „id” kluczowy element, ale to drobny szczegół)

-- 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

Myślę, że można coś zrobić z wykluczeniem ograniczeń. Nie wiem jednak, spójrz na moją edycję. – yokoloko

0

W aktualnych wersjach PostgreSQL (testowałem to w 9.6, ale zakładam, że działa w> = 9.2) można użyć funkcji kompilacji tstzrange(), jak wspomniano w innych komentarzach. Wartości Null będą domyślnie traktowane jako nieskończoność dodatnia lub ujemna, a ograniczenie CHECK nie będzie wtedy już jawnie potrzebne (jeśli wszystko w porządku, że czek jest tylko <=, a zakres może zaczynać się i kończyć z tą samą datą).Tylko rozszerzenie btree_gist jest nadal potrzebne:

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 && 
    ) 
); 
Powiązane problemy