299

このレイアウトのテーブルがあります:

CREATE TABLE Favorites
(
  FavoriteId uuid NOT NULL PRIMARY KEY,
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  MenuId uuid
)

これに似た一意の制約を作成したい:

ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);

ただし、これにより、同じ を持つ複数の行が許可され(UserId, RecipeId)ますMenuId IS NULL。メニューが関連付けられていないお気に入りを保存できるようNULLにしたいのですが、ユーザー/レシピのペアごとにこれらの行を最大で 1 つだけ保存したいと考えています。MenuId

私がこれまでに持っているアイデアは次のとおりです。

  1. null の代わりにハードコードされた UUID (すべてゼロなど) を使用します。
    ただし、MenuId各ユーザーのメニューには FK 制約があるため、ユーザーごとに特別な「null」メニューを作成する必要があり、面倒です。

  2. 代わりにトリガーを使用して null エントリの存在を確認してください。
    これは面倒だと思うので、可能な限りトリガーを避けるのが好きです。さらに、データが決して悪い状態にならないことを保証してくれるとは信じていません。

  3. それを忘れて、ミドルウェアまたは挿入関数にnullエントリが以前に存在していないかどうかを確認してください。この制約はありません。

Postgres 9.0 を使用しています。

私が見落としている方法はありますか?

4

4 に答える 4

459

2 つの部分インデックスを作成します。

CREATE UNIQUE INDEX favo_3col_uni_idx ON favorites (user_id, menu_id, recipe_id)
WHERE menu_id IS NOT NULL;

CREATE UNIQUE INDEX favo_2col_uni_idx ON favorites (user_id, recipe_id)
WHERE menu_id IS NULL;

(user_id, recipe_id)このように、 whereの組み合わせは 1 つしかなくmenu_id IS NULL、目的の制約を効果的に実装できます。

考えられる欠点:

  • を参照する外部キーを持つことはできません(user_id, menu_id, recipe_id)。(3 列幅の FK 参照が必要になる可能性は低いと思われます。代わりに PK 列を使用してください!)
  • CLUSTER部分索引に基づくことはできません。
  • 一致WHERE条件のないクエリでは、部分インデックスを使用できません。

完全なインデックスが必要な場合は、代わりにWHERE条件を削除することもできfavo_3col_uni_idx、要件は引き続き適用されます。
テーブル全体を構成するインデックスは、他のテーブルと重複して大きくなります。典型的なクエリとNULL値のパーセンテージによっては、これが役立つ場合とそうでない場合があります。極端な状況では、3 つすべてのインデックス (2 つの部分的なものと合計を一番上に) を維持することも役立つ場合があります。

これは、単一の nullable column、おそらく 2 つの場合に適したソリューションです。ただし、null 許容列の組み合わせごとに個別の部分インデックスが必要になるため、すぐに手に負えなくなります。そのため、数は二項的に増加します。複数の null 許容列については、代わりに以下を参照してください。

余談ですが、PostgreSQL では大文字と小文字が混在する識別子を使用しないことをお勧めします。

于 2011-11-27T21:34:56.823 に答える
98

MenuIdの合体を使用して一意のインデックスを作成できます。

CREATE UNIQUE INDEX
Favorites_UniqueFavorite ON Favorites
(UserId, COALESCE(MenuId, '00000000-0000-0000-0000-000000000000'), RecipeId);

「実生活」では決して発生しないCOALESCEのUUIDを選択する必要があります。実生活ではおそらくゼロのUUIDは表示されませんが、妄想的である場合はCHECK制約を追加できます(そして、彼らは本当にあなたを捕まえようとしているので...):

alter table Favorites
add constraint check
(MenuId <> '00000000-0000-0000-0000-000000000000')
于 2011-11-27T21:45:09.117 に答える
2

メニューが関連付けられていないお気に入りを別のテーブルに保存できます。

CREATE TABLE FavoriteWithoutMenu
(
  FavoriteWithoutMenuId uuid NOT NULL, --Primary key
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  UNIQUE KEY (UserId, RecipeId)
)
于 2011-11-27T21:29:51.800 に答える
-2

ここには意味上の問題があると思います。私の見解では、ユーザーは特定のメニューを準備するために(ただし1つだけ)お気に入りのレシピを持つことができます。(OPにはメニューとレシピが混在しています。間違っている場合は、以下のMenuIdとRecipeIdを交換してください)これは、{user、menu}がこのテーブルの一意のキーであることを意味します。そして、それは正確に1つのレシピを指している必要があります。ユーザーがこの特定のメニューのお気に入りのレシピを持っていない場合、この{user、menu}キーペアの行は存在しないはずです。また、代理キー(FaVouRiteId)は不要です。複合主キーはリレーショナルマッピングテーブルに対して完全に有効です。

これにより、テーブル定義が減少します。

CREATE TABLE Favorites
( UserId uuid NOT NULL REFERENCES users(id)
, MenuId uuid NOT NULL REFERENCES menus(id)
, RecipeId uuid NOT NULL REFERENCES recipes(id)
, PRIMARY KEY (UserId, MenuId)
);
于 2011-11-27T23:12:22.703 に答える