web-dev-qa-db-ja.com

2列目の外部キー制約は、3列目がNOT NULLの場合のみ

次の表を考えてみます。

_CREATE TABLE verified_name (
  id               SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  UNIQUE (name, email)
);

CREATE TABLE address (
  id               SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  verified_name_id INTEGER NULL REFERENCES verified_name(id)
);
_

_address.verified_name_id_がNULLでない場合、nameemailおよびaddressフィールドが参照される_verified_name_?

以下をaddressに追加してみました:

_FOREIGN KEY (name, email) REFERENCES verified_name(name, email)
_

...しかし、その制約は_verified_name_id_がNULLの場合でも適用されます。

_WHERE verified_name_id IS NOT NULL_のような句を含む partial index 構文に似たものを探していますが、そのような句を_FOREIGN KEY_制約に追加するだけでは機能しません。

現在の望ましくない解決策:

次の制約を_verified_name_に追加できます。

_UNIQUE (name, email),
UNIQUE (id, name, email)
_

そして、addressに対する次の制約:

_FOREIGN KEY (verified_name_id, name, email) REFERENCES verified_name(id, name, email)
_

...しかし、これは_verified_name_に追加したくない制約を作成します(これは有効な論理制約ですが、余計であり、パフォーマンスにわずかな影響があります)。

5
Jim Stewart

適切なソリューション

問題の核心はデータモデルです。 normalizedschema では、nameemailを重複して保存することはありません。次のようになります:

CREATE TABLE name (
  name_id          SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  verified         BOOLEAN NOT NULL DEFAULT FALSE,
  UNIQUE (name, email)
);

CREATE TABLE address (
  address_id       SERIAL PRIMARY KEY,
  name_id          INT REFERENCES name(name_id)
  ...
);

まだ検証されていない名前でUNIQUE制約を破ることを許可する必要がある場合は、それを partial UNIQUE INDEX と置き換えることができます(念頭に置いていたように)。

CREATE UNIQUE INDEX name_verified_idx ON name(name, email) WHERE verified;

持っているもので作業する

不幸な設計に悩まされている一方で、すでに見つけたソリューションは要件に完全に適合しています。 デフォルトのFOREIGN KEYMATCH SIMPLE動作は、

WHERE verified_name_id IS NOT NULLのような句を含む部分インデックス構文。

マニュアルの引用

MATCH SIMPLEでは、任意の外部キー列をnullにすることができます。それらのいずれかがnullの場合、その行は参照されるテーブルで一致する必要はありません。

(やや信頼性が低く、費用がかかる)代替案は、addressのINSERT/UPDATEのトリガーとverified_nameのINSERT/UPDATE/DELETEのトリガーです。

13