説明
この要件の定式化 解釈の余地を残します:
where UserRole.role_name
従業員の役割名が含まれています。
私の解釈:
UserRole
にエントリがあります role_name = 'employee'
があります 。
命名規則 はです 問題がありました(今すぐ更新)。 User
標準SQLおよびPostgresの予約語です。二重引用符で囲まれていない限り、識別子としては違法です。これはお勧めできません。正式な名前を使用するため、二重引用符で囲む必要はありません。
実装では問題のない識別子を使用しています。
問題
FOREIGN KEY
およびCHECK
制約は、リレーショナルの整合性を強化するための実証済みの気密ツールです。トリガーは強力で便利で用途の広い機能ですが、より洗練されており、厳密性が低く、設計エラーやコーナーケースの余地があります。
FK制約は最初は不可能に見えるため、ケースは困難です。PRIMARY KEY
が必要です。 またはUNIQUE
参照への制約-どちらもNULL値を許可しません。部分的なFK制約はありません。厳密な参照整合性からの脱出は、参照のNULL値のみです。 デフォルトのMATCH SIMPLE
による列 FK制約の動作。ドキュメントごと:
MATCH SIMPLE
外部キー列のいずれかをnullにすることができます。それらのいずれかがnullの場合、その行は参照されるテーブルで一致する必要はありません。
詳細を含むdba.SEの関連回答:
- 3番目の列がNULLでない場合にのみ2列の外部キー制約
回避策は、ブールフラグis_employee
を導入することです。 両側の従業員をマークするには、NOT NULL
を定義します users
、ただしNULL
にすることができます user_role
で :
解決策
これにより、要件が正確に強制されます。 、ノイズとオーバーヘッドを最小限に抑えながら:
CREATE TABLE users (
users_id serial PRIMARY KEY
, employee_nr int
, is_employee bool NOT NULL DEFAULT false
, CONSTRAINT role_employee CHECK (employee_nr IS NOT NULL = is_employee)
, UNIQUE (is_employee, users_id) -- required for FK (otherwise redundant)
);
CREATE TABLE user_role (
user_role_id serial PRIMARY KEY
, users_id int NOT NULL REFERENCES users
, role_name text NOT NULL
, is_employee bool CHECK(is_employee)
, CONSTRAINT role_employee
CHECK (role_name <> 'employee' OR is_employee IS TRUE)
, CONSTRAINT role_employee_requires_employee_nr_fk
FOREIGN KEY (is_employee, users_id) REFERENCES users(is_employee, users_id)
);
それだけです。
これらのトリガー オプションですが、追加されたタグを設定するのに便利なようにis_employee
をお勧めします 自動的に、何もする必要はありません 追加:
-- users
CREATE OR REPLACE FUNCTION trg_users_insup_bef()
RETURNS trigger AS
$func$
BEGIN
NEW.is_employee = (NEW.employee_nr IS NOT NULL);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF employee_nr ON users
FOR EACH ROW
EXECUTE PROCEDURE trg_users_insup_bef();
-- user_role
CREATE OR REPLACE FUNCTION trg_user_role_insup_bef()
RETURNS trigger AS
$func$
BEGIN
NEW.is_employee = true;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF role_name ON user_role
FOR EACH ROW
WHEN (NEW.role_name = 'employee')
EXECUTE PROCEDURE trg_user_role_insup_bef();
繰り返しになりますが、意味がなく、最適化されており、必要な場合にのみ呼び出されます。
SQLフィドル Postgres9.3のデモ。 Postgres9.1以降で動作するはずです。
主なポイント
-
ここで、
user_role.role_name = 'employee'
を設定する場合 、一致するuser.employee_nr
が必要です 最初。 -
employee_nr
は引き続き追加できます 任意 ユーザーであり、(その後)任意のuser_role
にタグを付けることができますis_employee
を使用 、実際のrole_name
に関係なく 。必要に応じて簡単に禁止できますが、この実装では必要以上の制限はありません。 -
users.is_employee
true
のみになります またはfalse
employee_nr
の存在を反映するように強制されますCHECK
による 制約。トリガーは、列の同期を自動的に維持します。false
を許可できます さらに、他の目的のために、デザインをわずかに更新するだけです。 -
user_role.is_employee
のルール わずかに異なります。role_name = 'employee'
の場合はtrueである必要があります 。CHECK
によって強制されます 制約し、トリガーによって自動的に設定されます。ただし、role_name
を変更することは許可されています 他の何かに移動し、is_employee
を保持します 。employee_nr
のユーザーは誰も言いませんでした 必須user_role
に対応するエントリを含める 、その逆です!繰り返しになりますが、必要に応じて追加で簡単に適用できます。 -
干渉する可能性のある他のトリガーがある場合は、次のことを考慮してください。
PostgreSQL 9.2.1でトリガー呼び出しのループを回避する方法
ただし、上記のトリガーは便宜上のものであるため、ルールに違反する可能性があることを心配する必要はありません。ルール自体はCHECK
で適用されます およびFK制約。例外はありません。 -
余談ですが、
is_employee
という列を配置しました 制約の最初のUNIQUE (is_employee, users_id)
理由 。users_id
すでにPKでカバーされているため、ここで2位になる可能性があります:
DB連想エンティティとインデックス作成