質問

以下に Rails モデルとして表されるものなど、ポリモーフィックな関連付けでは外部キーを使用できないのはなぜでしょうか?

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

class Article < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class Photo < ActiveRecord::Base
  has_many :comments, :as => :commentable
  #...
end

class Event < ActiveRecord::Base
  has_many :comments, :as => :commentable
end
役に立ちましたか?

解決

外部キーは 1 つの親テーブルのみを参照する必要があります。これは、SQL 構文とリレーショナル理論の両方の基礎です。

ポリモーフィックな関連付けとは、特定の列が 2 つ以上の親テーブルのいずれかを参照する可能性がある場合のことです。SQL でその制約を宣言する方法はありません。

ポリモーフィック アソシエーションの設計は、リレーショナル データベース設計のルールを打ち破ります。使用はお勧めしません。

いくつかの代替案があります。

  • 限定アーク: それぞれが 1 つの親を参照する複数の外部キー列を作成します。これらの外部キーの 1 つだけを NULL 以外にできるようにします。

  • 関係を逆にします: 3 つの多対多テーブルを使用し、それぞれがコメントとそれぞれの親を参照します。

  • コンクリートスーパーテーブル: 暗黙的な「コメント可能な」スーパークラスの代わりに、各親テーブルが参照する実際のテーブルを作成します。次に、コメントをそのスーパーテーブルにリンクします。疑似 Rails コードは次のようになります (私は Rails ユーザーではないので、これを文字通りのコードではなくガイドラインとして扱います)。

    class Commentable < ActiveRecord::Base
      has_many :comments
    end
    
    class Comment < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Article < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Photo < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Event < ActiveRecord::Base
      belongs_to :commentable
    end
    

私のプレゼンテーションではポリモーフィックな関連性についても取り上げています SQL の実践的なオブジェクト指向モデル, 、そして私の本 SQL アンチパターン:データベースプログラミングの落とし穴を回避する.


コメントを再確認してください:はい、外部キーが指していると思われるテーブルの名前を示す別の列があることは知っています。この設計は SQL の外部キーではサポートされていません。

たとえば、コメントを挿入し、その親テーブルの名前として「Video」という名前を付けた場合はどうなりますか? Comment?「Video」という名前のテーブルは存在しません。挿入はエラーで中止されるべきですか?どのような制約が違反されていますか?RDBMS は、この列が既存のテーブルに名前を付けることになっているということをどのようにして知るのでしょうか?大文字と小文字を区別しないテーブル名はどのように処理されますか?

同様に、 Events テーブルですが、行があります Comments イベントが親であることを示している場合、結果はどうなるでしょうか?ドロップテーブルを中止する必要がありますか?行を入れる必要があります Comments 孤児になる?次のような別の既存のテーブルを参照するように変更する必要がありますか? Articles?を指していた ID 値を実行します Events ~を指すときは意味がある Articles?

これらのジレンマはすべて、ポリモーフィック アソシエーションがデータの使用に依存しているという事実によるものです (つまり、文字列値) を参照してメタデータ (テーブル名) を参照します。これは SQL ではサポートされていません。データとメタデータは別のものです。


あなたの「コンクリートスーパーテーブル」という提案を理解するのに苦労しています。

  • 定義する Commentable Rails モデル定義の単なる形容詞ではなく、実際の SQL テーブルとして。他の列は必要ありません。

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • テーブルを定義する Articles, Photos, 、 そして Events の「サブクラス」として Commentable, 、主キーを外部キー参照にすることによって Commentable.

    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • を定義します Comments 外部キーを持つテーブル Commentable.

    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • を作成したいときは、 Article (たとえば)、次の場所に新しい行を作成する必要があります。 Commentable あまりにも。にとっても同様です Photos そして Events.

    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1
    INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2
    INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3
    INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
  • を作成したいときは、 Comment, に存在する値を使用します。 Commentable.

    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • 特定のコメントを照会したい場合 Photo, 、いくつかの結合を実行します。

    SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id)
    LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id)
    WHERE p.id = 2;
    
  • コメントの ID しかなく、それがどのようなコメント可能なリソースに対するコメントであるかを知りたい場合。このため、コメント可能テーブルで参照するリソースを指定すると便利な場合があります。

    SELECT commentable_id, commentable_type FROM Commentable t
    JOIN Comments c ON (t.id = c.commentable_id)
    WHERE c.id = 42;
    

    次に、次のクエリを実行して、それぞれのリソース テーブル (写真、記事など) からデータを取得する必要があります。 commentable_type どのテーブルに参加するか。SQL ではテーブルに明示的に名前を付ける必要があるため、同じクエリ内でこれを行うことはできません。同じクエリ内のデータ結果によって決定されるテーブルに結合することはできません。

確かに、これらの手順の一部は、Rails で使用されている規則を破っています。しかし、適切なリレーショナル データベース設計という点では、Rails の規則は間違っています。

他のヒント

ビルKarwinは、外部キーはSQLが実際にネイティブの概念多型の関係を持っていないに起因する多型の関係で使用することができないことが正しいです。外部キーを持っていることのあなたの目標は、参照整合性を施行する場合しかし、あなたは、トリガーを経由して、それをシミュレートすることができます。これは、DBの特定を取得しますが、以下、私は多型の関係の外部キーのカスケード削除動作をシミュレートするために作成したいくつかの最近のトリガーです。

CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$
  BEGIN
    DELETE FROM subscribers
    WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id;
    RETURN NULL;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER cascade_brokerage_subscriber_delete
AFTER DELETE ON brokerages
FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers();


CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$
  BEGIN
    DELETE FROM subscribers
    WHERE referrer_type = 'Agent' AND referrer_id = OLD.id;
    RETURN NULL;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER cascade_agent_subscriber_delete
AFTER DELETE ON agents
FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();

私のコードでbrokeragesテーブルのレコードまたはagentsテーブル内のレコードがsubscribersテーブル内のレコードに関連付けることができます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top