簡體   English   中英

Postgres日期重疊約束

[英]Postgres date overlapping constraint

我有這樣一張桌子:

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)

編輯:我找到了一些東西:

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/

我唯一的問題是它不能使用空值作為結束時間戳,我想用無限值替換它但不起作用。

好的,我最終做到了這一點:

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;

nb:如果你沒有空時間戳,就不需要使用時間擴展,你可以使用我的問題中指定的box方法。

在最新的postgres版本中(我在9.6中進行了測試,但我認為它在> = 9.2中工作)你可以使用其他一些注釋中提到的函數tstzrange() 默認情況下,空值將被視為正或負無窮大,然后不再明確需要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 &&
    )
);

這是一個難題,因為約束只能引用“當前行” ,並且可能不包含子查詢。 (否則簡單的解決方案是在檢查中添加一些NOT EXISTS()子查詢)

指定為列約束的檢查約束應僅引用該列的值,而出現在表約束中的表達式可引用多個列。

目前,CHECK表達式不能包含子查詢,也不能引用當前行的列以外的變量。

流行的解決方法是:使用觸發功能來執行臟工作(或使用規則系統,大多數人都棄用)

因為大多數人喜歡觸發器,我會在這里重新發布一個規則系統hack ...(它沒有額外的“id”鍵元素,但這是一個小細節)

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

如何為一組列創建唯一約束:

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

在您的情況下,如果表已經存在,您將需要ALTER TABLE,請查看它對您有幫助的文檔:
- DDL約束
- 更改表格

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM