Program Tip

다형성 연관에 외래 키가없는 이유는 무엇입니까?

programtip 2020. 10. 23. 08:20
반응형

다형성 연관에 외래 키가없는 이유는 무엇입니까?


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

외래 키는 하나의 상위 테이블 만 참조해야합니다. 이것은 SQL 구문과 관계 이론의 기본입니다.

다형성 연관은 주어진 열이 둘 이상의 상위 테이블 중 하나를 참조 할 수있는 경우입니다. SQL에서 제약 조건을 선언 할 수있는 방법은 없습니다.

Polymorphic Associations 디자인은 관계형 데이터베이스 디자인의 규칙을 위반합니다. 나는 그것을 사용하지 않는 것이 좋습니다.

몇 가지 대안이 있습니다.

  • 배타적 호 : 각각 하나의 부모를 참조하는 여러 외래 키 열을 만듭니다. 이러한 외래 키 중 정확히 하나가 NULL이 아닐 수 있도록 강제합니다.

  • 관계 반전 : 3 개의 다 대다 테이블을 사용하고 각 테이블은 주석과 각 상위를 참조합니다.

  • 구체적인 수퍼 테이블 : 암시 적 "주석 가능"수퍼 클래스 대신 각 상위 테이블이 참조하는 실제 테이블을 만듭니다. 그런 다음 주석을 해당 수퍼 테이블에 연결하십시오. Pseudo-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의 Practical Object-Oriented Models 및 저서 SQL Antipatterns : Preventing the Pitfalls of Database Programming 에서 다형성 연관성에 대해서도 다룹니다 .


Re your comment : 예, 외래 키가 가리키는 테이블의 이름을 기록하는 또 다른 열이 있다는 것을 알고 있습니다. 이 디자인은 SQL의 외래 키에서 지원되지 않습니다.

예를 들어 주석을 삽입하고 해당 상위 테이블의 이름으로 "Video"라는 이름을 입력하면 Comment어떻게됩니까? "비디오"라는 테이블이 없습니다. 삽입이 오류와 함께 중단되어야합니까? 어떤 제약이 위반되고 있습니까? RDBMS는이 열이 기존 테이블의 이름을 지정해야한다는 것을 어떻게 알 수 있습니까? 대소 문자를 구분하지 않는 테이블 이름을 어떻게 처리합니까?

마찬가지로 Events테이블 을 삭제 했지만 Comments이벤트를 상위로 표시하는 이있는 경우 결과는 무엇입니까? 삭제 테이블을 중단해야합니까? 의 행 Comments이 분리 되어야합니까 ? 같은 다른 기존 테이블을 참조하도록 변경해야합니까 Articles? Events가리키는 데 사용되는 ID 값이 의미가 Articles있습니까?

이러한 딜레마는 모두 다형성 연관이 메타 데이터 (테이블 이름)를 참조하기 위해 데이터 (예 : 문자열 값)를 사용하는 데 의존한다는 사실 때문입니다. 이것은 SQL에서 지원되지 않습니다. 데이터와 메타 데이터는 분리되어 있습니다.


"콘크리트 수퍼 테이블"제안에 대해 머리를 감싸는 데 어려움을 겪고 있습니다.

  • 정의 Commentable레일스 모델 정의에 실제 SQL 테이블로가 아닌 형용사입니다. 다른 열은 필요하지 않습니다.

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • 테이블 Articles, PhotosEvents의 "하위 클래스"를 정의하여 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너무. 그래서 너무에 PhotosEvents.

    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 만 있고 댓글이 어떤 댓글이있는 리소스를 찾고 싶을 때. 이를 위해 Commentable 테이블이 참조하는 리소스를 지정하는 것이 도움이 될 수 있습니다.

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

    Then you'd need to run a second query to get data from the respective resource table (Photos, Articles, etc.), after discovering from commentable_type which table to join to. You can't do it in the same query, because SQL requires that tables be named explicitly; you can't join to a table determined by data results in the same query.

Admittedly, some of these steps break the conventions used by Rails. But the Rails conventions are wrong with respect to proper relational database design.


Bill Karwin is correct that foreign keys cannot be used with polymorphic relationships due to SQL not really having a native concept polymorphic relationships. But if your goal of having a foreign key is to enforce referential integrity you can simulate it via triggers. This gets DB specific but below is some recent triggers I created to simulate the cascading delete behavior of a foreign key on a polymorphic relationship:

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();

In my code a record in the brokerages table or a record in the agents table can relate to a record in the subscribers table.

참고URL : https://stackoverflow.com/questions/922184/why-can-you-not-have-a-foreign-key-in-a-polymorphic-association

반응형