sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQLのクロステーブル制約

    説明

    この要件の定式化 解釈の余地を残します:
    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連想エンティティとインデックス作成



    1. MySQL、MariaDB、PostgreSQL、MongoDBの運用レポート

    2. SQLServerのforループの構文

    3. Oracleで休止状態:StringプロパティをCLOB列にマッピング

    4. プライマリインデックスとセカンダリインデックスの正確な違いは何ですか?