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

SQLAlchemyの複雑な外部キー制約

    汚いトリックなしでそれを実装できます 。 外部キーを拡張する 選択したオプションを参照してvariable_idを含める choice_idに加えて 。

    これが実際のデモです。一時的なテーブルなので、簡単に遊ぶことができます:

    CREATE TABLE systemvariables (
      variable_id int PRIMARY KEY
    , choice_id   int
    , variable    text
    );
       
    INSERT INTO systemvariables(variable_id, variable) VALUES
      (1, 'var1')
    , (2, 'var2')
    , (3, 'var3')
    ;
    
    CREATE TABLE variableoptions (
      option_id   int PRIMARY KEY
    , variable_id int REFERENCES systemvariables ON UPDATE CASCADE ON DELETE CASCADE
    , option      text
    , UNIQUE (option_id, variable_id)  -- needed for the FK
    );
    
    ALTER TABLE systemvariables
      ADD CONSTRAINT systemvariables_choice_id_fk
      FOREIGN KEY (choice_id, variable_id) REFERENCES variableoptions(option_id, variable_id);
    
    INSERT INTO variableoptions  VALUES
      (1, 'var1_op1', 1)
    , (2, 'var1_op2', 1)
    , (3, 'var1_op3', 1)
    , (4, 'var2_op1', 2)
    , (5, 'var2_op2', 2)
    , (6, 'var3_op1', 3)
    ;
    

    関連するオプションを選択できます:

    UPDATE systemvariables SET choice_id = 2 WHERE variable_id = 1;
    UPDATE systemvariables SET choice_id = 5 WHERE variable_id = 2;
    UPDATE systemvariables SET choice_id = 6 WHERE variable_id = 3;
    

    しかし、ラインから外れることはありません:

    UPDATE systemvariables SET choice_id = 7 WHERE variable_id = 3;
    UPDATE systemvariables SET choice_id = 4 WHERE variable_id = 1;
    
    ERROR:  insert or update on table "systemvariables" violates foreign key constraint "systemvariables_choice_id_fk"
    DETAIL: Key (choice_id,variable_id)=(4,1) is not present in table "variableoptions".
    

    まさにあなたが望んでいたもの。

    すべてのキー列NOT NULL

    私はこの後の答えでより良い解決策を見つけたと思います:

    • 相互に依存する挿入を処理する方法

    コメント内の@ypercubeの質問に対処し、関連付けが不明なエントリを回避するために、すべてのキー列をNOT NULLにします。 、外部キーを含む。

    循環依存は通常それを不可能にします。古典的な鶏卵です 問題:もう一方をスポーンするには、両方の一方が最初に存在する必要があります。しかし、自然はそれを回避する方法を見つけました、そしてPostgresもそうしました:延期可能な外部キー制約

    CREATE TABLE systemvariables (
      variable_id int PRIMARY KEY
    , variable    text
    , choice_id   int NOT NULL
    );
    
    CREATE TABLE variableoptions (
      option_id   int PRIMARY KEY
    , option      text
    , variable_id int NOT NULL REFERENCES systemvariables
         ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
    , UNIQUE (option_id, variable_id) -- needed for the foreign key
    );
    
    ALTER TABLE systemvariables
    ADD CONSTRAINT systemvariables_choice_id_fk FOREIGN KEY (choice_id, variable_id)
       REFERENCES variableoptions(option_id, variable_id) DEFERRABLE INITIALLY DEFERRED; -- no CASCADING here!
    

    新規 変数と関連するオプションは、同じトランザクションに挿入する必要があります:

    BEGIN;
    
    INSERT INTO systemvariables (variable_id, variable, choice_id)
    VALUES
      (1, 'var1', 2)
    , (2, 'var2', 5)
    , (3, 'var3', 6);
    
    INSERT INTO variableoptions (option_id, option, variable_id)
    VALUES
      (1, 'var1_op1', 1)
    , (2, 'var1_op2', 1)
    , (3, 'var1_op3', 1)
    , (4, 'var2_op1', 2)
    , (5, 'var2_op2', 2)
    , (6, 'var3_op1', 3);
    
    END;
    

    NOT NULL 制約を延期することはできず、すぐに適用されます。ただし、外部キー制約は可能 、そのように定義したからです。トランザクションの最後にチェックされるため、鶏が先か卵が先かという問題が回避されます。

    この編集済み シナリオ、両方の外部キーが延期される 。変数とオプションは任意の順序で入力できます。
    1つのステートメントの両方のテーブルに関連するエントリを入力すると、遅延不可能なFK制約を使用して機能させることもできます。 リンクされた回答で詳しく説明されているように、CTEを使用します。

    最初の外部キー制約にCASCADEがないことに気付いたかもしれません。 修飾子。 (variableoptions.variable_idへの変更を許可することは意味がありません カスケードバックします。

    一方、2番目の外部キーにはCASCADEがあります 修飾子であり、DEFERRABLEで定義されています それにもかかわらず。これにはいくつかの制限があります。マニュアル:

    NO ACTION以外の参照アクション 制約が延期可能であると宣言されている場合でも、チェックを延期することはできません。

    NO ACTION デフォルトです。

    したがって、INSERTの参照整合性チェック 延期されますが、DELETEで宣言されたカスケードアクション およびUPDATE そうではありません。以下は、各ステートメントの後に制約が適用されるため、PostgreSQL9.0以降では許可されていません。

    UPDATE option SET var_id = 4 WHERE var_id = 5;
    DELETE FROM var WHERE var_id = 5;
    

    詳細:

    • 制約が定義されたDEFERRABLEINITIALLYIMMEDIATEはまだ延期されていますか?


    1. error_logではなくすべてのPHPエラーをデータベースに出力する

    2. mySQLとpostgreSQLのgroupby句、なぜpostgreSQLでエラーが発生するのですか?

    3. PostgreSQLで2つの日付の間の労働時間を計算します

    4. SQLServerで整数を10進数に変換する方法