7장 다형성 연관

여러 개의 부모테이블로부터 하나의 자식테이블을 공유할 수 있다.
이에 대해 오류가 발생할 상황이 만들어진다.

  1. 자식 테이블을 여러 개의 자식 테이블을 참조하는 FK를 생성할 수 없다. 결국 아래와 같은 DDL은 성립되지 않는다.

CREATE TABLE Comment (
Comment_id number PRIMARY KEY,
Bug_id number NOT NULL,
author_id number NOT NULL,
comment_date date NOT NULL,
comment varchar2(4000) number NOT NULL
FOREIGN KEY (issue_id)
REFFERNCES Bugs(issue_id) OR FeatureRequests(issue_id)
);

  1. 여러 테이블을 조회하기 위하여 다음과 같이 잘못된 SQL을 작성할 수 있다.
    이렇게 사용한 이유는 데이터베이스에서 사용되는 "테이블명"이란 메타정보를 문자열로 관리하고 있기 때문에 SQL바로 적용할 수 있다고 오해할 수 있기 때문이다.

SELECT c.*, i.summary, i.status
FROM Comments as c
JOIN c.issue_type AS I USING (isuue_id);

7.1 목표 : 여러 부모 참조

Comments테이블은 Bugs테이블의 행이나 FeatureRequests테이블의 행 중 하나와만 대응한다.
이를 ERD로 표기하면 아래와 같다. (Exclusive 관계)

7.2 안티패턴 : 이중 목적의 FK사용

이중 목적의 FK사용에 대한 해법을 일컬어 다형성 연관(Polymorphic Associations) 또는 난잡한 연관(Promiscuous Association)이라고 한다.

다형성 연관 정의

다형성 연관을 작동하게 하려면 FK컬럼인 issue_id컬럼에 들어가는 값에 대응하여 참조하는 테이블정보를 추가적으로 관리하여야 한다.


CREATE TABLE Comment (
Comment_id number PRIMARY KEY,
Issue_type VARCHAR2(20), -- bugs 또는 featureRequests 입력
Issue_id number NOT NULL,
Bug_id number NOT NULL,
author_id number NOT NULL,
comment_date date NOT NULL,
comment varchar2(4000) number NOT NULL
);

FK는 하나의 테이블만 참조할 수 있기 때문에 다형성 연관을 사용할 경우에는 이 연관을 메타데이터에 선언할 수 없기 때문에 FK선언을 할 수 없다.
그 결과로 다음과 같은 단점이 드러나게 된다.

  • Comments테이블의 issue_id는 부모테이블의 값과 대응되도록 강제할 수 없게 된다.
  • 정합성 보장도 DB레벨에서는 할 수 없게 된다.
  • 그렇기 때문에 값에 테이블명인지 아닌지도 알 수 없다.
다형성 연관에서의 조회

예제에서 사용되는 Comments테이블의 issue_id값은 Bugs테이블이나 FeatureRequests테이블 중 한 쪽에만 있을 수도 있고 양쪽 모두 있을 수도 있고 양쪽 모두 없을 수도 있다.


SELECT *
FROM Bugs AS b JOIN Comments AS c 
  ON (b.issue_id = c.issue_id AND c.issue_type = 'Bugs')
WHERE b.issue_id = 1234;

위와 같이 되어 있다면 Bugs테이블의 내용은 잘 동작한다.
그러나 FeatureRequests테이블의 내용은 나오지 않는다. SQL수행 시 관련된 테이블과의 관계를 다음처럼 명시해야 한다.


SELECT *
FROM Comments AS c 
LEFT OUTER JOIN Bugs AS b 
  ON (b.issue_id = c.issue_id AND c.issue_type = 'Bugs')
LEFT OUTER JOIN FeatureRequests AS f 
  ON (f.issue_id = c.issue_id AND c.issue_type = 'FeatureRequests')
WHERE b.issue_id = 1234;

비 객체지향 예제

부모테이블에 따라 구분되어 사용되는 값이 많아진다면 점점 컬럼이 2개씩(부모가 2개라면..)로 증가하게 되고 복잡하게 될 것이다.

7.3 안티패턴 인식 방법

테이블 설계 시 아래와 같은 말이 들린다면 안티패턴인지 식별할 필요가 있다.

  • 이 태깅 스키마는 데이터베이스 내의 어떤 리소스에도 태그(또는 다른 속성)을 달 수 있다.
    : 무제한적인 유연성을 주장할 때
  • 우리 데이터베이스 설계에서는 FK를 선언할 수 없다.
    : 안 하는 것이 아니라 못한다 이다.
  • entity_type 컬럼의 용도가 모지?
    : 각 행의 부모 테이블이 무엇인지 알려주는 컬럼

7.4 안티패턴 사용이 합당한 경우

합당한 경우가 없다. 단 특정 객체관계 프로그래밍 프레임워크를 사용하는 경우 이 안티패턴사용이 불가피한 경우가 있다. 이 경우 애플리케이션 로직을 캡슐화하여 참조정합성 유지에 도움을 줄 수 있다.

7.5 해법 : 관계단순화

다형성 연관의 단점을 피하면서 필요한 데이터 모델을 지원하기 위해서는 데이터베이스를 다시 설계하는 것이 낫다.

역참조

교차테이블 생성

자식테이블 Comments는 여러 부모테이블을 참조할 수 없으므로 Comments테이블과 각 부모테이블과의 교차 테이블을 생성하여 각 부모테이블의 FK뿐아니라 Comments테이블의 FK도 포함 시킨다.


CREATE TABLE BugsComment (
Issue_id number NOT NULL,
Comment_id number NOT NULL,
PRIMARY KEY (Issue_id, Comment_id)
FOREIGN KEY (issue_id) REFERENCES Bugs(issue_id),
FOREIGN KEY (Comment_id) REFERENCES Comments(Comment_id),
);
CREATE TABLE FeaturesComment (
Issue_id number NOT NULL,
Comment_id number NOT NULL,
PRIMARY KEY (Issue_id, Comment_id)
FOREIGN KEY (issue_id) REFERENCES FeatureRequests(issue_id),
FOREIGN KEY (Comment_id) REFERENCES Comments(Comment_id)
);

신호등설치

하나의 덧글이 한 번만 참조됨을 보장받아야 하는 경우 다음처럼 UNIQUE키를 설정할 수 있다.
CREATE TABLE BugsComment (
Issue_id number NOT NULL,
Comment_id number NOT NULL,
UNIQUE KEY (comment_id),
PRIMARY KEY (Issue_id, Comment_id)
FOREIGN KEY (issue_id) REFERENCES Bugs(issue_id),
FOREIGN KEY (Comment_id) REFERENCES Comments(Comment_id),
);

공통 수퍼테이블 생성

Issue와 같은 조상 테이블을 사용한다면 FK를 통해 데이터베이스 정합성을 강제할 수 있다.

  • 최초작성일 : 2011년 12월 06일
  • 이 문서는 오라클클럽 코어 오라클 데이터베이스 스터디 모임에서 작성하였습니다.
  • {*}이 문서의 내용은 인사이트(insight) 에서 출간한 'SQL AntiPatterns : 개발자가 알아야 할 25가지 SQL 함정과 해법'를 참고하였습니다.*