두 개의 가능한 테이블 중 하나에 MySQL 외래 키를 사용할 수 있습니까?

StackOverflow https://stackoverflow.com/questions/441001

  •  22-07-2019
  •  | 
  •  

문제

내 문제는 다음과 같습니다. 테이블이 세 개 있습니다.지역, 국가, 주.국가는 지역 안에 있을 수 있고, 주는 지역 안에 있을 수 있습니다.지역은 먹이사슬의 정점이다.

이제 두 개의 열이 있는 Popular_areas 테이블을 추가하겠습니다.Region_id 및 Popular_place_id입니다.Popular_place_id를 두 국가 모두의 외래 키로 만들 수 있습니까? 또는 상태.ID가 국가 또는 주를 어느 쪽이든 설명하는지 확인하려면 Popular_place_type 열을 추가해야 할 것입니다.

도움이 되었습니까?

해결책

당신이 설명하는 것을 다형성 연관이라고합니다.즉, "외부 키" 열에는 대상 테이블 집합 중 하나에 존재해야 하는 ID 값이 포함되어 있습니다.일반적으로 대상 테이블은 일부 공통 슈퍼클래스 데이터의 인스턴스가 되는 등 어떤 방식으로든 관련되어 있습니다.또한 각 행에서 참조되는 대상 테이블을 지정할 수 있도록 외래 키 열 옆에 또 다른 열이 필요합니다.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

SQL 제약 조건을 사용하여 다형성 연관을 모델링할 방법은 없습니다.외래 키 제약 조건은 항상 참조합니다. 하나 대상 테이블.

다형성 연관은 Rails 및 Hibernate와 같은 프레임워크에서 지원됩니다.하지만 그들은 이 기능을 사용하려면 SQL 제약 조건을 비활성화해야 한다고 명시적으로 말합니다.대신, 애플리케이션이나 프레임워크는 참조가 충족되는지 확인하기 위해 동등한 작업을 수행해야 합니다.즉, 외래 키의 값이 가능한 대상 테이블 중 하나에 존재합니다.

다형성 연관은 데이터베이스 일관성 시행과 관련하여 약합니다.데이터 무결성은 동일한 참조 무결성 논리가 적용된 데이터베이스에 액세스하는 모든 클라이언트에 따라 달라지며, 적용에는 버그가 없어야 합니다.

데이터베이스 적용 참조 무결성을 활용하는 몇 가지 대체 솔루션은 다음과 같습니다.

대상당 하나의 추가 테이블을 만듭니다. 예를 들어 popular_states 그리고 popular_countries, 이는 참조 states 그리고 countries 각기.이러한 "인기 있는" 테이블 각각은 사용자 프로필도 참조합니다.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

즉, 사용자가 즐겨찾는 인기 장소를 모두 가져오려면 두 테이블을 모두 쿼리해야 한다는 뜻입니다.하지만 이는 데이터베이스에 의존하여 일관성을 유지할 수 있음을 의미합니다.

만들기 places 슈퍼 테이블로서의 테이블. Abie가 언급했듯이 두 번째 대안은 인기 있는 장소가 다음과 같은 테이블을 참조하는 것입니다. places, 이는 둘 다의 상위 항목입니다. states 그리고 countries.즉, 주와 국가 모두 외래 키를 가지고 있습니다. places (이 외래 키를 기본 키로 만들 수도 있습니다. states 그리고 countries).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

두 개의 열을 사용합니다. 두 개의 대상 테이블 중 하나를 참조할 수 있는 하나의 열 대신 두 개의 열을 사용하십시오.이 두 열은 NULL;실제로 그 중 하나만이 아닌 것이어야 합니다.NULL.

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

관계 이론의 관점에서 다형성 연관은 다음을 위반합니다. 첫 번째 정규형, 왜냐하면 popular_place_id 사실상 다음 두 가지 의미를 갖는 열입니다.그것은 주이거나 국가이다.당신은 사람의 정보를 저장하지 않을 것입니다 age 그리고 그들의 phone_number 단일 열에 저장해야 하며 같은 이유로 두 항목을 모두 저장하면 안 됩니다. state_id 그리고 country_id 단일 열에.이 두 속성이 호환 가능한 데이터 유형을 가지고 있다는 사실은 우연입니다.그들은 여전히 ​​​​다른 논리적 실체를 의미합니다.

다형성 연관도 위반합니다. 제3정규형, 열의 의미는 외래 키가 참조하는 테이블의 이름을 지정하는 추가 열에 따라 달라지기 때문입니다.제3정규형에서 테이블의 속성은 해당 테이블의 기본 키에만 의존해야 합니다.


@SavasVedova의 댓글:

테이블 정의나 예제 쿼리를 보지 않고 귀하의 설명을 따랐는지 잘 모르겠지만 단순히 여러 개의 쿼리가 있는 것처럼 들립니다. Filters 각 테이블에는 중앙 키를 참조하는 외래 키가 포함되어 있습니다. Products 테이블.

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...

어떤 유형에 가입하려는지 알면 제품을 특정 유형의 필터에 쉽게 가입할 수 있습니다.

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

필터 유형을 동적으로 만들려면 SQL 쿼리를 구성하는 애플리케이션 코드를 작성해야 합니다.SQL에서는 쿼리를 작성할 때 테이블을 지정하고 수정해야 합니다.개별 행에 있는 값을 기반으로 조인된 테이블을 동적으로 선택할 수는 없습니다. Products.

유일한 다른 옵션은 다음 사이트에 가입하는 것입니다. 모두 외부 조인을 사용하여 테이블을 필터링합니다.일치하는 product_id가 없는 항목은 단일 null 행으로 반환됩니다.하지만 여전히 하드코딩을 해야 합니다. 모두 조인된 테이블이 있고 새 필터 테이블을 추가하는 경우 코드를 업데이트해야 합니다.

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

모든 필터 테이블을 조인하는 또 다른 방법은 순차적으로 조인하는 것입니다.

SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...

하지만 이 형식을 사용하려면 여전히 모든 테이블에 대한 참조를 작성해야 합니다.그 문제를 해결할 수 없습니다.

다른 팁

이것은 세계에서 가장 우아한 솔루션은 아니지만 사용할 수 있습니다. 콘크리트 테이블 상속 이 일을하기 위해.

개념적으로 당신은 세 가지 유형의 장소가 상속받는 "인기있는 영역이 될 수있는 것들"클래스의 개념을 제안하고 있습니다. 예를 들어 이것을 테이블로 표시 할 수 있습니다. places 각 행에 행과 일대일 관계가있는 경우 regions, countries, 또는 states. (지역, 국가 또는 주간에 공유되는 속성은이 장소 테이블로 밀어 넣을 수 있습니다.) 귀하의 popular_place_id 그런 다음 장소 테이블의 행에 대한 외국의 키 참조가되어 지역, 국가 또는 주로 이어집니다.

연관 유형을 설명하기 위해 두 번째 열이있는 솔루션은 Rails가 다형성 연관성을 처리하는 방법이지만 일반적으로 그 팬은 아닙니다. Bill은 왜 다형성 연관성이 당신의 친구가 아닌지에 대해 자세히 설명합니다.

다음은 복합 키를 사용하여 Bill Karwin의 "Supertable"접근법에 대한 수정입니다. ( place_type, place_id ) 인식 된 정상적인 형태 위반을 해결하기 위해 :

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);

이 디자인이 모든 행에 대해 보장 할 수없는 것 places 행이 있습니다 states 또는 countries (그러나 둘 다가 아님). 이것은 SQL에서 외래 키의 한계입니다. 완전한 SQL-92 표준을 준수하는 DBMS에서는 동일하게 달성 할 수있는 연기 가능한 테이블 간 제약 조건을 정의 할 수 있지만 트랜잭션이 포함되며 DBMS는 아직 시장에 출시하지 못했습니다.

나는이 실이 오래되었다는 것을 알고 있지만, 나는 이것을 보았고 해결책이 떠오르고 나는 그것을 거기에 버릴 것이라고 생각했다.

지역, 국가 및주는 계층 구조에 사는 지리적 위치입니다.

지리적 _location_type라는 도메인 테이블을 만들어 세 행 (지역, 국가, 상태)으로 채울 수 있습니다.

다음으로, 세 가지 위치 테이블 대신 지리적 _location_type_id의 외국 키가있는 단일 지리적 _location 테이블을 만듭니다 (따라서 인스턴스가 지역, 국가 또는 상태인지 알 수 있음).

상태 인스턴스가 FKEY를 부모 국가 인스턴스로 유지하도록 하여이 테이블을 자체 참조하여 계층 구조를 모델링하여 FKEY를 부모 영역 인스턴스로 유지합니다. 지역 인스턴스는 그 fkey에서 null을 유지합니다. 이것은 당신이 세 테이블 (1- 지역과 국가, 국가와 국가 사이의 많은 관계를 가질 수있는 것과 다르지 않습니다)은 이제 하나의 테이블에 있습니다.

altern_user_location 테이블은 사용자와 georgraphical_location 간의 범위 해상도 테이블입니다 (많은 사용자가 많은 장소를 좋아할 수 있습니다).

soooo…

enter image description here

CREATE TABLE [geographical_location_type] (
    [geographical_location_type_id] INTEGER NOT NULL,
    [name] VARCHAR(25) NOT NULL,
    CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)

-- Add 'Region', 'Country' and 'State' instances to the above table


CREATE TABLE [geographical_location] (
   [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
    [name] VARCHAR(1024) NOT NULL,
    [geographical_location_type_id] INTEGER NOT NULL,
    [geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
    CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)

CREATE TABLE [user] (
    [user_id] BIGINT NOT NULL,
    [login_id] VARCHAR(30) NOT NULL,
    [password] VARCHAR(512) NOT NULL,
    CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)


CREATE TABLE [popular_user_location] (
    [popular_user_location_id] BIGINT NOT NULL,
    [user_id] BIGINT NOT NULL,
    [geographical_location_id] BIGINT NOT NULL,
    CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)

ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] 
    FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])



ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] 
    FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] 
    FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] 
    FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

대상 DB가 무엇인지 확신하지 못했습니다. 위는 MS SQL Server입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top