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