説明
この要件の定式化 解釈の余地を残します:
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_employeetrueのみになります またはfalseemployee_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連想エンティティとインデックス作成