[sql] 널 (null) 열로 고유 제한 조건 작성

이 레이아웃이있는 테이블이 있습니다.

CREATE TABLE Favorites
(
  FavoriteId uuid NOT NULL PRIMARY KEY,
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  MenuId uuid
)

다음과 유사한 고유 제약 조건을 만들고 싶습니다.

ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);

그러나 이렇게하면 (UserId, RecipeId)if 가 동일한 여러 행이 허용 됩니다 MenuId IS NULL. 내가 허용 할 NULLMenuId어떤 메뉴를 관련 지을 수있는 즐겨 찾기를 저장하지만, 난 단지 사용자 / 조리법 쌍 당이 행의 대부분 하나를 원한다.

지금까지 내가 가진 아이디어는 다음과 같습니다.

  1. null 대신 하드 코딩 된 UUID (예 : 모든 0)를 사용하십시오.
    그러나 MenuId각 사용자의 메뉴에 FK 제약이 있으므로 번거로운 모든 사용자에 대해 특별한 “널”메뉴를 만들어야합니다.

  2. 대신 트리거를 사용하여 널 항목이 있는지 확인하십시오.
    나는 이것이 번거롭고 가능한 한 트리거를 피하는 것을 좋아합니다. 또한 데이터가 결코 나쁜 상태에 있지 않다는 것을 보증하지 않습니다.

  3. 이를 잊고 미들웨어 또는 삽입 함수에 널 항목이 존재하는지 점검하십시오.이 제한 조건이 없습니다.

Postgres 9.0을 사용하고 있습니다.

내가 간과하는 방법이 있습니까?



답변

두 개의 부분 인덱스를 만듭니다 .

CREATE UNIQUE INDEX favo_3col_uni_idx ON favorites (user_id, menu_id, recipe_id)
WHERE menu_id IS NOT NULL;

CREATE UNIQUE INDEX favo_2col_uni_idx ON favorites (user_id, recipe_id)
WHERE menu_id IS NULL;

이런 식으로 (user_id, recipe_id)where menu_id IS NULL제약 조건을 효과적으로 구현하는 where 조합은 하나만 가능 합니다.

가능한 단점 : 외래 키 참조 (user_id, menu_id, recipe_id)를 가질 수없고 CLUSTER부분 인덱스를 기반 으로 할 수 없으며 일치하는 WHERE조건이없는 쿼리 는 부분 인덱스를 사용할 수 없습니다. (3 열 너비의 FK 참조를 원하지 않을 것 같습니다. 대신 PK 열을 사용하십시오).

완전한 색인 이 필요한 경우 WHERE조건을 제거 할 수 있으며 favo_3col_uni_idx요구 사항은 여전히 ​​적용됩니다.
이제 전체 테이블을 구성하는 인덱스가 다른 테이블과 겹치면서 커집니다. 일반적인 쿼리 및 NULL값 의 백분율에 따라 유용하거나 유용하지 않을 수 있습니다. 극단적 인 상황에서는 세 개의 인덱스 (두 개의 부분 인덱스와 총계)를 모두 유지하는 데 도움이 될 수도 있습니다.

따로 : PostgreSQL에서 대소 문자를 혼합 하여 사용하지 않는 것이 좋습니다 .


답변

MenuId에서 통합 된 고유 인덱스를 만들 수 있습니다.

CREATE UNIQUE INDEX
Favorites_UniqueFavorite ON Favorites
(UserId, COALESCE(MenuId, '00000000-0000-0000-0000-000000000000'), RecipeId);

“실제”에서는 발생하지 않는 COALESCE에 대한 UUID를 선택하기 만하면됩니다. 실생활에서는 UUID가 전혀 보이지 않지만 편집증이라면 CHECK 제약 조건을 추가 할 수 있습니다 ( 실제로 당신을 얻을 수 있기 때문에 …) :

alter table Favorites
add constraint check
(MenuId <> '00000000-0000-0000-0000-000000000000')


답변

메뉴가없는 즐겨 찾기를 별도의 테이블에 저장할 수 있습니다.

CREATE TABLE FavoriteWithoutMenu
(
  FavoriteWithoutMenuId uuid NOT NULL, --Primary key
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  UNIQUE KEY (UserId, RecipeId)
)


답변

여기에 의미 론적 문제가 있다고 생각합니다. 내 관점에서, 사용자는 (하지만 할 수 있습니다 하나의 특정 메뉴를 준비하는) 마음에 드는 조리법을. (OP에는 메뉴와 레시피가 섞여 있습니다. 틀린 경우 아래의 MenuId와 RecipeId를 바꾸십시오.) 이는 {user, menu}가이 테이블에서 고유 한 키 여야 함을 의미합니다. 그리고 정확히 하나의 레시피를 가리켜 야합니다 . 사용자가이 특정 메뉴에 대해 선호하는 레시피가없는 경우이 {user, menu} 키 쌍에 대한 행이 없어야합니다 . 또한 대리 키 (FaVouRiteId)는 불필요합니다. 복합 기본 키는 관계형 매핑 테이블에 완벽하게 유효합니다.

그러면 테이블 정의가 줄어 듭니다.

CREATE TABLE Favorites
( UserId uuid NOT NULL REFERENCES users(id)
, MenuId uuid NOT NULL REFERENCES menus(id)
, RecipeId uuid NOT NULL REFERENCES recipes(id)
, PRIMARY KEY (UserId, MenuId)
);


답변