我有一个表格,描述了一台机器在不同时间安装的软件版本:
machine_id::integer, version::text, datefrom::timestamp, dateto::timestamp
我希望实施一个限制条件,以确保没有日期范围重叠,即不可能在同一时间将多个软件版本安装在一台机器上。
如何在SQL中实现这一目标?我正在使用PostgreSQL v8.4。
我有一个表格,描述了一台机器在不同时间安装的软件版本:
machine_id::integer, version::text, datefrom::timestamp, dateto::timestamp
我希望实施一个限制条件,以确保没有日期范围重叠,即不可能在同一时间将多个软件版本安装在一台机器上。
如何在SQL中实现这一目标?我正在使用PostgreSQL v8.4。
SELECT FOR UPDATE
选取machines表中的行,以便没有其他事务可以同时插入可能会冲突的数据。与此同时(如果我正确地阅读手册,自9.2版本以来),postgreSQL已经为范围类型添加了支持。
使用这些范围类型,问题突然变得非常简单(以下示例摘自手册):
CREATE TABLE reservation (
during tsrange,
EXCLUDE USING gist (during WITH &&)
);
就是这样。测试(也是从手册中复制的):
INSERT INTO reservation VALUES
('[2010-01-01 11:30, 2010-01-01 15:00)');
插入 0 1
INSERT INTO reservation VALUES
('[2010-01-01 14:45, 2010-01-01 15:45)');
错误:冲突的键值违反了排除约束“reservation_during_excl” 详细信息:键(during)=(["2010-01-01 14:45:00",“2010-01-01 15:45:00")) 冲突 已存在的键(during)=(["2010-01-01 11:30:00",“2010-01-01 15:00:00"))。
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE machines(
machine_id integer,
version text,
during tsrange,
EXCLUDE USING gist ( machine_id with =, during with &&)
);
表格中具有相同ID的机器将不会重叠。
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedObject) data type integer has no default operator class for access method "gist" HINT: You must specify an operator class for the index or define a default operator class for the data type.
- Zaffer
CREATE TABLE reservations (
datefrom timestamp,
dateto timestamp,
EXCLUDE USING gist (tsrange(datefrom, dateto) WITH &&)
);
-- 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;
您是否真的想要一个像标题中提到的 CHECK 约束?这是不可能的,因为 CHECK 约束只能一次处理一行。不过,可能可以使用触发器来实现这个功能...
exclude using gist ( resourceId with =, during with &&)
。 - yankee