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


31

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

따라서 올바른 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.