여러 개의 외래 키를 쉼표로 구분하여 사용하고 있습니까? 그렇다면 왜 그렇습니까?


31

두 개의 테이블이 있습니다 : DealDealCategories. 하나의 거래에는 많은 거래 카테고리가있을 수 있습니다.

따라서 올바른 DealCategories구조는 다음 구조로 불리는 테이블을 만드는 것입니다 .

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

그러나 아웃소싱 팀은 다음과 같이 여러 카테고리를 Deal테이블 에 저장했습니다 .

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

나는 그들이 한 일이 잘못되었다고 생각하지만 이것이 왜 옳지 않은지를 분명히 설명하는 방법을 모르겠습니다.

이것이 잘못되었다는 것을 어떻게 설명해야합니까? 아니면 내가 틀린 사람 일 수도 있고 이것이 허용 되는가?



7
더 이상 피해를 입히기 전에 팀을 아웃소싱 한 화재 ... (-_-)
Rafa

답변:


49

예, 끔찍한 생각입니다.

가는 대신 :

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

이제 가야합니다 :

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

그런 다음 응용 프로그램 코드에서 해당 쉼표 목록을 개별 숫자로 나누고 데이터베이스를 별도로 쿼리해야합니다.

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

이 디자인 반 패턴은 관계형 모델링에 대한 완전한 오해 (테이블을 무서워 할 필요는 없습니다. 테이블은 친구입니다. 테이블을 사용하십시오) 또는 쉼표로 구분 된 목록을 가져 와서 나누는 것이 더 빠르다는 기묘하게 잘못된 생각에서 비롯됩니다. 응용 프로그램 코드에서 링크 테이블을 추가하는 것보다 결코 아닙니다 . 세 번째 옵션은 외래 키를 설정할 수있을 정도로 SQL에 대해 확신이없고 유능하지 않다는 것입니다.

SQL Antipatterns (Karwin, 2010)는이 반 패턴 ( 'Jaywalking'이라고 함), 15-23 페이지에 전체 장을 할당합니다. 또한 저자는 비슷한 질문을 SO 에 게시했습니다 . 그가 지적한 요점은이 예제에 적용됩니다.

  • 특정 카테고리의 모든 거래를 쿼리하는 것은 다소 복잡합니다 (문제를 해결하는 가장 쉬운 방법은 정규식이지만 정규식 자체는 문제임).
  • 외래 키 관계가 없으면 참조 무결성을 강화할 수 없습니다. DealCategory nr을 삭제 한 경우 그런 다음 애플리케이션 코드에서 # 26 카테고리에 대한 참조를 찾아 각 거래를 거쳐 삭제해야합니다. 이것은 데이터 계층에서 처리해야하는 것이며 응용 프로그램에서 처리해야하는 것은 매우 나쁜 일 입니다.
  • 집계 쿼리 ( COUNT, SUM등), 다시, '거의 불가능'에 '복잡'에서 다릅니다. 개발자에게 해당 카테고리의 거래 수와 함께 모든 카테고리 목록을 얻는 방법을 문의하십시오. 적절한 디자인으로, 그것은 네 줄의 SQL입니다.
  • 업데이트가 훨씬 어려워집니다 (즉, 5 개 범주의 거래가 있지만 두 개를 제거하고 다른 세 개를 추가하려고 함). 그것은 적절한 디자인을 가진 세 줄의 SQL입니다.
  • 결국 VARCHAR리스트 길이 제한에 직면하게됩니다. 4,000자가 넘는 쉼표로 구분 된 목록이 있으면 어쨌든 몬스터가 느리게 진행될 가능성이 높습니다.
  • 데이터베이스에서 목록을 가져 와서 분리 한 다음 다른 쿼리에 대해 데이터베이스로 돌아가는 것은 본질적으로 하나의 쿼리보다 느립니다.

TLDR : 근본적으로 결함이있는 디자인이며, 확장 성이 떨어지며, 가장 간단한 쿼리조차도 복잡성을 증가 시키며 즉시 사용이 가능하여 애플리케이션 속도가 느려집니다.


1
Simon, 누군가 같은 질문 ( dba.stackexchange.com/questions/17824/… )을했지만 같은 FK와 PK가 같은 테이블에 왜 3FN을 제동하는지 분명하지 않습니다.
jcho360

2
나는 그들이 거래와 카테고리 사이에 다 대다 관계를 갖기를 원하는지 또는 카테고리의 계층 구조를 원하는지 여부를 완전히 확신하지 못했습니다. 어느 쪽이든, 링크 테이블 대신 쉼표로 구분 된 필드가되는 것은 나쁜 생각입니다.
Simon Righarts 22시 47 분

4

그러나 아웃소싱 팀은 다음과 같이 여러 가지 카테고리를 거래 테이블에 저장했습니다.

DealId (PK) DealCategory-여기에 여러 거래 ID를 쉼표로 구분하여 18,25,32로 저장합니다.

주어진 거래에 대한 카테고리 쿼리해야하는 경우 실제로 좋은 디자인 입니다.

그러나 주어진 카테고리의 모든 거래를 알고 싶다면 끔찍합니다.

또한 업데이트, 카운트, 조인 등과 같은 다른 작업을 수행하기가 매우 어렵고 오류가 발생하기 쉽습니다.

비정규 화는 그 자리를 대신하지만 동일한 데이터에 대해 수행 할 수있는 다른 모든 데이터를 희생하면서 한 가지 유형의 쿼리에 대해 최적화해야합니다. 항상 하나의 패턴으로 쿼리한다는 것을 알고 있다면 비정규 화 된 디자인을 사용하는 것이 유리할 수 있습니다. 그러나 쿼리 유형에 더 많은 유연성이 필요할 경우 표준화 된 디자인을 사용하십시오.

다른 형태의 최적화와 마찬가지로 비정규 화가 정당화되는지 여부를 결정하기 전에 어떤 쿼리를 실행할지 알아야합니다.


1
쉼표로 구분 된 자식 ID가있는 문자열이 도움이된다고 생각하십니까? 내 말은, 응용 프로그램이 먼저 읽은 다음 ID를 구문 분석하고와 같은 모든 자식을 쿼리해야한다는 것을 의미합니다 select * from DealCategories where DealId in (1,2,3,4,...). 데이터베이스 설계와 관련하여 나보다 더 많은 경험이 있으므로 어떤 경우에는 매우 특별한 경우 에 이러한 "극한 조정"에 대한 충분한 이유가있을 수 있습니다 . 이것을 정당화하는 나의 유일한 아이디어는 selectDeal / DealCategory에 매우 높은 부하입니다. 이것은 테이블을 생성하고 생성하는 것 외에도 DB 디자인 지식이없는 아웃소싱 팀과 매우 비슷합니다.
Erik Hart

1
@ErikHart, 이것은 비정규 화이며 도움 있지만 내 요점은 실행 해야하는 쿼리에 전적으로 달려 있다는 것입니다. 비정규 화는 최적화하는 하나의 쿼리를 제외하고 모든 쿼리의 성능을 저하시키는 것이 맞습니다. 하나의 쿼리 만 실행하면되고 다른 쿼리는 신경 쓰지 않아도됩니다. 그러나 일반적으로 데이터를 다양한 방식으로 쿼리 할 수있는 유연성을 원하기 때문에 드문 경우입니다.
Bill Karwin

1
@ErikHart, 해당 아웃소싱 팀에이 데이터에 대한 쿼리가 하나만 포함 된 프로젝트 사양이 제공된 경우 해당 쿼리에 대해서만 최적화를 설계 할 수있었습니다. 다시 말해, "당신은 그것을 요구했다, 당신은 그것을 얻었다." 그러나 아웃소싱 제공 업체는 향후 데이터 사용 계획을 세울 이유가 없습니다. 사양에 작성된 내용에 따라 애플리케이션을 구현합니다.
Bill Karwin

1

열의 여러 값이 첫 번째 정규형에 위배됩니다.

테이블이 데이터베이스에 연결되어 있기 때문에 속도 향상도 전혀 없습니다. 먼저 문자열을 읽고 파싱 한 다음 "거래"에 대한 모든 범주를 선택해야합니다.

올바른 구현은 DealId 및 DealCategoryId를 가진 "DealDealCategories"와 같은 접합 테이블입니다.

잘못된 계층 구조?

또한 Deal Categories의 다른 DealCategory에 대한 FK는 Deal Categories의 계층 구조 / 트리를 잘못 구현 한 것처럼 보입니다. 부모 ID (일명 인접 목록) 관계를 통해 나무를 다루는 것은 고통스러운 일입니다!

계층 구조를 구현할 때 중첩 된 세트 (읽기 쉽지만 수정하기 어려운) 및 클로저 테이블 (최상의 전체 성능, 그러나 메모리 사용량이 많을 것-아마도 Deal 카테고리에 너무 많지 않음)을 확인하십시오!

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