25

私はこのようなテーブルを持っています:

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/についてもっと知りたい場合

私の唯一の問題は、終了タイムスタンプとしてnull値では機能しないことです。無限の値に置き換えることを考えましたが、同様に機能しません。

4

4 に答える 4

29

わかりました、私はこれをすることになりました:

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でネイティブになる一時的な拡張機能と、9.1で拡張機能として利用できるbtree_gistをインストールする必要がありました。CREATE EXTENSION btree_gist;

nb:nullタイムスタンプがない場合は、一時的な拡張を使用する必要はありません。私の質問で指定されているように、boxメソッドを使用できます。

于 2012-05-21T10:17:31.930 に答える
7

tstzrange()最新のpostgresバージョン(9.6でテストしましたが、> = 9.2で動作していると思います)では、他のコメントで述べられているように、組み込み関数を使用できます。ヌル値はデフォルトで正または負の無限大として扱われ、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 &&
    )
);
于 2017-10-05T07:46:53.387 に答える
0

制約は「現在の行」のみを参照でき、サブクエリを含まない場合があるため、これは難しい問題です。(それ以外の場合、簡単な解決策はNOT EXISTS()、チェックにサブクエリを追加することです)

列制約として指定されたチェック制約は、その列の値のみを参照する必要がありますが、テーブル制約に表示される式は、複数の列を参照できます。

現在、CHECK式にサブクエリを含めることも、現在の行の列以外の変数を参照することもできません。

一般的な回避策は次のとおりです。ダーティな作業を行うトリガー関数を使用する(またはほとんどの人が非推奨にするルールシステムを使用する)

ほとんどの人はトリガーを好むので、ここでルールシステムのハックを再投稿します...(余分な「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;

                                                                                                                      
于 2012-05-16T10:50:20.613 に答える
-4

列のグループに一意性制約を作成する方法:

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

あなたの場合、テーブルがすでに存在する場合はALTER TABLEを実行する必要があります。ドキュメントを確認してください:
-DDL制約
-ALTER Table

于 2012-05-16T10:04:30.743 に答える