web-dev-qa-db-ja.com

NULL列を持つ一意の制約を作成します

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

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を持つ複数の行を許可します。 NULLMenuIdにメニューが関連付けられていないお気に入りを保存できるようにしたいのですが、ユーザー/レシピのペアごとにこれらの行の1つだけが必要です。

これまでのアイデアは次のとおりです。

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

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

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

Postgres 9.0を使用しています。

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

211

作成 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)の組み合わせは1つだけで、menu_id IS NULLがあり、目的の制約を効果的に実装します。

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

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

余談: PostgreSQLの大文字小文字混合識別子 を使用しないことをお勧めします。

328

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

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

「現実」では決して発生しないCOALESCEのUUIDを選択する必要があります。おそらく実際にはゼロのUUIDが表示されることはないでしょうが、偏執的な場合はCHECK制約を追加できます(そしてtheyが本当にあなたを手に入れようとしているので...):

alter table Favorites
add constraint check
(MenuId <> '00000000-0000-0000-0000-000000000000')
60
mu is too short

関連するメニューのないお気に入りを別のテーブルに保存できます。

CREATE TABLE FavoriteWithoutMenu
(
  FavoriteWithoutMenuId uuid NOT NULL, --Primary key
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  UNIQUE KEY (UserId, RecipeId)
)
2
ypercubeᵀᴹ

ここには意味的な問題があると思います。私の見解では、ユーザーは特定のメニューを準備するために(ただし、1つだけ)お気に入りのレシピを持つことができます。 (OPにはメニューとレシピが混在しています。間違っている場合は、下のMenuIdとRecipeIdを交換してください)これは、{user、menu}がこのテーブルの一意のキーであることを意味します。そして、それはexactly one recipeを指しているはずです。ユーザーがこの特定のメニューにお気に入りのレシピを持たない場合、この{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)
);
0
wildplasser