同じ object_id と user_id を持つアクティブなレコードが常に 1 つだけであることを確認する必要がある状況があります。代表的な表は次のとおりです。
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true,
created_at timestamptz default now()
);
一度に 1 つのアクティブ レコードのみということは、次のような一連の挿入を行うことができるということです。
insert into actions (object_id, user_id, active) values (1, 1, true);
insert into actions (object_id, user_id, active) values (1, 1, false);
しかし、その後の
insert into actions (object_id, user_id, active) values (1, 1, true);
この時点で、object_id = 1 および user_id = 1 のアクティブなタプルが既に 1 つ存在するため、失敗するはずです。
PostgreSQL 8.4 を使用しています。
面白そうなこの投稿を見ましたが、その Oracle 固有のものです。
この投稿も見ましたが、トランザクションの分離レベルに関してもっと注意が必要です。リードコミットモードではそのままでは機能しないと思います。
私の質問は、この種の制約が不明な場合に利用できる他のオプションは何ですか?
編集: 最初のセットの 3 番目の挿入を削除しました。私はそれが例を混乱させたと思います。コンテキストに役立つように、created_at タイム スタンプも追加しました。繰り返しますが、(object_id、user_id、false) タプルは複数存在できますが、(object_id、user_id、true) タプルは 1 つだけです。
更新: Craig の回答を受け入れましたが、似たようなものに出くわす可能性のある他の人のために、別の (最適ではありませんが) 解決策があります。
CREATE TABLE action_consistency (
object_id integer,
user_id integer,
count integer default 0,
primary key (object_id, user_id),
check (count >= 0 AND count <= 1)
);
CREATE OR REPLACE FUNCTION keep_action_consistency()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF NEW.active THEN
UPDATE action_consistency
SET count = count + 1
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id;
INSERT INTO action_consistency (object_id, user_id, count)
SELECT NEW.object_id, NEW.user_id, 1
WHERE NOT EXISTS (SELECT 1
FROM action_consistency
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id);
ELSE
-- assuming insert will be active for simplicity
UPDATE action_consistency
SET count = count - 1
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER ensure_action_consistency AFTER INSERT OR UPDATE ON actions
FOR EACH ROW EXECUTE PROCEDURE keep_action_consistency();
追跡テーブルを使用する必要があります。明らかな理由であることを願っていますが、これはまったく望ましくありません。これは、アクションに個別の (object_id、user_id) ごとに追加の行があることを意味します。
@Craig Ringer の回答を受け入れたもう 1 つの理由は、他のテーブルの actions.id への外部キー参照があり、特定のアクション タプルの状態が変化したときに非アクティブになることです。これが、このシナリオで履歴テーブルがあまり理想的でない理由です。コメントと回答ありがとうございます。