web-dev-qa-db-ja.com

アイテムごとに最大1つのnullを強制する一意の制約

PostgreSQL 9.3を使用しています。特定のタイプのいくつかのアイテムを製造していると仮定します。いつでも、特定のタイプのアイテムを最大で1つだけ生産しています。次の表は、製造されたアイテムの履歴を示しています。manufactured_untilnullである行がある場合があります-これらは現在生産されているアイテムです。

create table item
(
  id int,
  type_id int,
  manufactured_from timestamp,
  manufactured_until timestamp
)

サンプルデータ:

1  | 101  | 1.1.2000    | 31.12.2012
2  | 102  | 1.4.2003    | 1.1.2010
3  | 101  | 1.1.2013    | 
4  | 102  | 2.1.2010    | 4.5.2014
5  | 102  | 5.5.2014    | 

次のロジックが成り立ちます。各アイテムタイプについて、現時点で生成されているアイテムは1つだけです(manufactured IS NULL)。この例では、レコード(6, 101, 27.8.2014, NULL)を追加できません。

これを保護するUNIQUE制約を記述したいと思います。出来ますか?ボーナスポイントの場合、1つのアイテムタイプの間隔が重ならないようにするための合理的に複雑な方法はありますか?

5
vektor

部分一意インデックスはこれを行う必要があります:

create unique index max_one_null 
   on item (type_id) 
where manufactured_until is null;

ボーナスポイントの場合、1つのアイテムタイプの間隔が重ならないようにするための合理的に複雑な方法はありますか

範囲タイプと除外制約を調べます。それらはこの問題のために特別に設計されました。

このようなもの(試されていない):

create table item
(
  id int,
  type_id int,
  manufactured_during tsrange
);

現在、単一の列しかないため、挿入は少し異なります。

insert into item 
  (id, type_id, manufactured_during)
values 
  (1, 101, '[2000-01-01,2012-12-31]'),
  (1, 101, '[2013-01-01,)');

[2000-01-01,2012-12-31]は、両方の日付を含む閉じた間隔を定義します。 [2013-01-01,)は、オープンインターバルを終了なしで定義します(現在のテーブルデザインでmanufactured_until is nullにマップします)

次に、範囲を保護するために除外制約を追加します。

alter table item
  add constraint check_manufactured_range
  exclude using Gist (type_id with =, manufactured_during with &&);

制約によって重複するものがないことが確認されるため、一意のインデックスは必要ありません。

マニュアルの詳細情報: http://www.postgresql.org/docs/current/static/rangetypes.html

インターネットで「Postgres除外制約」を検索すると、このトピックに関するいくつかのプレゼンテーションとブログ投稿が見つかります。

追伸:何かがいつ製造されたかを定義する間隔に時刻を含めたくないので、実際にはdaterange(「タイムスタンプ範囲」の代わりに)が必要なようです。