SQL의 전체 참여 제한 조건과 다 대다 관계 구현


17

다음 엔터티 관계 다이어그램에 묘사 된 시나리오를 SQL에서 어떻게 구현해야합니까?

총 참여 제약과 다 대다 관계

도시 된 바와 같이, 모든 A엔티티 유형 발생은 적어도 하나의 B 대응 물 (이중 연결선으로 표시됨)과 관련되어야 하며 그 반대도 마찬가지 입니다. 다음 세 가지 테이블을 만들어야한다는 것을 알고 있습니다.

    CREATE TABLE A
    (
        a INT NOT NULL,
        CONSTRAINT A_PK PRIMARY KEY (a)
    );

    CREATE TABLE B
    (
        b INT NOT NULL,
        CONSTRAINT B_PK PRIMARY KEY (b)
    );

    CREATE TABLE R
    (
        a INT NOT NULL,
        b INT NOT NULL,
        CONSTRAINT R_PK      PRIMARY KEY (a, b),
        CONSTRAINT R_to_A_FK FOREIGN KEY (a)
            REFERENCES A (a),
        CONSTRAINT R_to_B_FK FOREIGN KEY (b)
            REFERENCES B (b)
    );

그러나, 어떤이의 구현에 대해 총 참여 제약 (즉, 시행 하는 것이 하나의 예 A또는이 B에 관여 한 최소 다른과의 관계 발생)?

답변:


16

SQL에서는 쉽지 않지만 불가능하지는 않습니다. 이를 DDL만으로 시행하려면 DBMS가 DEFERRABLE제한 조건 을 구현해야 합니다. 이 작업을 수행 할 수 있습니다 (Postgres에서 작동하는지 확인할 수 있음).

-- lets create first the 2 tables, A and B:
CREATE TABLE a 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT a_pk PRIMARY KEY (aid) 
 );

CREATE TABLE b 
( bid INT NOT NULL,
  aid INT NOT NULL,
  CONSTRAINT b_pk PRIMARY KEY (bid) 
 );

-- then table R:
CREATE TABLE r 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT r_pk PRIMARY KEY (aid, bid),
  CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,  
  CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
 );

여기까지는 "정상적인"디자인으로 모든 A것이 0, 1 또는 다수 BB관련 될 수 있고 모든 것이 0, 하나 또는 다수 와 관련 될 수 있습니다 A.

"전체 참여"제한에는 역순으로 제한이 필요합니다 ( AB참조 R). 데 FOREIGN KEY(X에서 Y와 X에 Y에서) 원의 (a "닭이 먼저 냐 달걀이 먼저 냐"문제)을 형성하고, 우리는 적어도이 될 그들 중 하나를 필요로하는 이유의 그 반대 방향으로 제약 DEFERRABLE. 이 경우에는 두 개의 원이 있습니다 (A -> R -> A 그리고 B -> R -> B우리는 두 가지, 연기 제약 조건이 필요합니다

-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
  ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r 
    DEFERRABLE INITIALLY DEFERRED ;

ALTER TABLE b
  ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r 
    DEFERRABLE INITIALLY DEFERRED ;

그런 다음 데이터를 삽입 할 수 있는지 테스트 할 수 있습니다. 가 있습니다 INITIALLY DEFERRED필요하지 않습니다. 우리는 제약 조건을 정의 할 수 DEFERRABLE INITIALLY IMMEDIATE있었지만 SET CONSTRAINTS트랜잭션 중에 명령문을 사용하여 연기해야합니다. 그러나 모든 경우에 단일 트랜잭션으로 테이블에 삽입해야합니다.

-- insert data 
BEGIN TRANSACTION ;
    INSERT INTO a (aid, bid)
    VALUES
      (1, 1),    (2, 5),
      (3, 7),    (4, 1) ;

    INSERT INTO b (aid, bid)
    VALUES
      (1, 1),    (1, 2),
      (2, 3),    (2, 4),
      (2, 5),    (3, 6),
      (3, 7) ;

    INSERT INTO r (aid, bid)
    VALUES
      (1, 1),    (1, 2),
      (2, 3),    (2, 4),
      (2, 5),    (3, 6),
      (3, 7),    (4, 1),
      (4, 2),    (4, 7) ; 
 END ;

SQLfiddle 에서 테스트되었습니다 .


DBMS에 DEFERRABLE제한 조건 이없는 경우 한 가지 해결 방법은 A (bid)B (aid)열을로 정의하는 것 NULL입니다. INSERT절차 / 문은 다음에 먼저 삽입해야 A하고 B(에 널 (null)을 넣어bidaid다음, 각각)에 삽입 R한 다음에서 관련 null이 아닌 값으로 위의 널 (null) 값을 업데이트 R.

이 방법으로, DBMS는 DDL 혼자 그러나으로 요구 사항을 적용마다하지 않습니다 INSERT(그리고UPDATEDELETEMERGE절차를 고려하고 그에 따라 조정 및 사용자는 그들과하지가 테이블에 직접 쓰기 액세스를 사용하도록 제한 할 필요가되어야한다).

에 서클을 갖는 FOREIGN KEY제약 조건 것은 많은 모범 사례에 의해 고려되지 않으며 좋은 이유로 복잡성이 그 중 하나입니다. 예를 들어 널 입력 가능 열을 사용하는 두 번째 방법을 사용하면 DBMS에 따라 행 업데이트 및 삭제를 추가 코드로 수행해야합니다. 예를 들어 SQL Server에서는 ON DELETE CASCADEFK 서클이있을 때 계단식 업데이트 및 삭제가 허용되지 않기 때문에 넣을 수 없습니다 .

이 관련 질문에 대한 답변도 읽으십시오 :
특권 자녀와 일대 다 관계를 맺는 방법?


또 다른 세 번째 접근법 (위의 질문에서 내 대답 참조)은 원형 FK를 완전히 제거하는 것입니다. 그래서, 코드의 첫 번째 부분을 유지 (테이블 A, B, R단지 R에서 A와 B와 외래 키) (실제로 그것을 단순화) 거의 그대로, 우리는 또 다른 테이블 추가 A에서 "이 있어야 하나"관련 항목을 저장하기를 B. 따라서 A (bid)A_one (bid)은 B에서 A 로의 역 관계에 대해 동일하게 수행됩니다.

CREATE TABLE a 
( aid INT NOT NULL,
  CONSTRAINT a_pk PRIMARY KEY (aid) 
 );

CREATE TABLE b 
( bid INT NOT NULL,
  CONSTRAINT b_pk PRIMARY KEY (bid) 
 );

-- then table R:
CREATE TABLE r 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT r_pk PRIMARY KEY (aid, bid),
  CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,  
  CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
 );

CREATE TABLE a_one 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT a_one_pk PRIMARY KEY (aid),
  CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
 );

CREATE TABLE b_one
( bid INT NOT NULL,
  aid INT NOT NULL,
  CONSTRAINT b_one_pk PRIMARY KEY (bid),
  CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
 );

첫 번째와 두 번째 방법의 차이점은 순환 FK가 없으므로 계단식 업데이트 및 삭제가 제대로 작동한다는 것입니다. "총 참여"의 시행은 2 차 접근법에서와 같이 DDL 단독이 아니며 적절한 절차에 따라 수행되어야한다 (INSERT/UPDATE/DELETE/MERGE ). 두 번째 접근 방식과의 약간의 차이점은 모든 열을 nullable이 아닌 것으로 정의 할 수 있다는 것입니다.


또 다른 네 번째 접근 방법 ( 위 의 질문 에서 @Aaron Bertrand의 답변 참조 )은 DBMS에서 사용할 수있는 경우 필터링 된 / 부분 고유 인덱스 를 사용 하는 것 R입니다 (이 경우 테이블에 두 개가 필요합니다 ). 이것은 두 개의 추가 테이블이 필요하지 않다는 점을 제외하고는 세 번째 접근 방식과 매우 유사합니다. "전체 참여"제한 조건은 여전히 ​​코드에 의해 적용되어야합니다.


네 번째 접근 방식 (약간 숨겨져 있음)은 실제로 완벽합니다. 예를 들어 postgres 에 대해서는 postgresql.org/docs/9.6/static/indexes-partial.html 예 11-3을 참조하십시오 .
Danilo

@Danilo 저는 최대 1 회의 총 참여가 보장되는 것이 얼마나 완벽한 지 알고 있습니다 (postgre 예제에서 성공한 일부 추가 필드를 기반으로 함). 이 스레드의 실제 질문 인 적어도 하나의 성공이 있는지 확인하는 것이 어떻게 도움이되는지 알 수 없습니다. 좀 더 자세히 설명해 주시겠습니까?
Alexander Mihailov

3

직접 할 수 없습니다. 우선, B가 존재하지 않는 A에 대한 레코드를 삽입 할 수는 없지만 A 레코드가 없으면 B 레코드를 작성할 수 없습니다. 트리거와 같은 것을 사용하여 적용하는 방법에는 여러 가지가 있습니다. 모든 삽입을 확인하고 하나 이상의 해당 레코드가 AB 링크 테이블에 남아 있는지 삭제해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.