Việc sử dụng nhiều khóa ngoại được phân tách bằng dấu phẩy là sai, và nếu vậy, tại sao?


31

Có hai bảng: DealDealCategories. Một thỏa thuận có thể có nhiều loại thỏa thuận.

Vì vậy, cách thích hợp là tạo một bảng được gọi DealCategoriesvới cấu trúc sau:

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

Tuy nhiên, nhóm thuê ngoài của chúng tôi đã lưu trữ nhiều danh mục trong Dealbảng theo cách này:

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

Tôi cảm thấy rằng những gì họ đã làm là sai, nhưng tôi không biết làm thế nào để giải thích rõ ràng tại sao điều này không đúng.

Làm thế nào tôi nên giải thích cho họ rằng điều này là sai? Hoặc có thể tôi là người sai và điều này được chấp nhận?



7
sa thải đội ngũ bên ngoài ngay lập tức trước khi họ gây hại thêm nữa ... (-_-)
Rafa

Câu trả lời:


49

Vâng, đó là một ý tưởng khủng khiếp.

Thay vì đi:

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

Bây giờ bạn phải đi:

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

Sau đó, bạn cần thực hiện các công cụ trong mã ứng dụng của mình để chia danh sách dấu phẩy đó thành các số riêng lẻ, sau đó truy vấn cơ sở dữ liệu một cách riêng biệt:

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

Mô hình thiết kế này bắt nguồn từ sự hiểu lầm hoàn toàn về mô hình quan hệ (Bạn không phải sợ các bảng. Bàn là bạn của bạn. Sử dụng chúng), hoặc một niềm tin sai lầm kỳ lạ là nhanh hơn để lấy danh sách được phân tách bằng dấu phẩy và phân tách nó trong mã ứng dụng hơn là thêm một bảng liên kết ( không bao giờ được). Tùy chọn thứ ba là họ không đủ tự tin / đủ năng lực với SQL để có thể thiết lập khóa ngoại, nhưng nếu đó là trường hợp họ không nên làm gì với thiết kế mô hình quan hệ.

SQL Antipotypes (Karwin, 2010) dành toàn bộ một chương cho antipotype này (mà ông gọi là 'Jaywalking'), trang 15-23. Ngoài ra, tác giả đã đăng một câu hỏi tương tự tại SO . Những điểm chính anh ấy lưu ý (như áp dụng cho ví dụ này) là:

  • Truy vấn cho tất cả các giao dịch trong một danh mục cụ thể khá phức tạp (cách dễ nhất để giải quyết vấn đề đó là một biểu thức chính quy, nhưng một biểu thức chính quy là một vấn đề trong chính nó).
  • Bạn không thể thực thi tính toàn vẹn tham chiếu mà không có mối quan hệ khóa nước ngoài. Nếu bạn xóa DealC Category nr. # 26, sau đó, trong mã ứng dụng của bạn, phải trải qua từng giao dịch tìm kiếm tài liệu tham khảo cho danh mục # 26 và xóa chúng. Đây là một cái gì đó nên được xử lý ở lớp dữ liệu và phải xử lý nó trong ứng dụng của bạn là một điều rất tồi tệ .
  • Các truy vấn tổng hợp ( COUNT, SUMv.v.), một lần nữa, thay đổi từ 'phức tạp' đến 'gần như không thể'. Hỏi nhà phát triển của bạn làm thế nào họ có được danh sách tất cả các danh mục với số lượng giao dịch trong danh mục đó. Với một thiết kế phù hợp, đó là bốn dòng SQL.
  • Các cập nhật trở nên khó khăn hơn nhiều (tức là bạn có một thỏa thuận trong năm loại, nhưng bạn muốn loại bỏ hai và thêm ba loại khác). Đó là ba dòng SQL với thiết kế phù hợp.
  • Cuối cùng, bạn sẽ chạy vào VARCHARgiới hạn độ dài danh sách. Mặc dù nếu bạn có một danh sách được phân tách bằng dấu phẩy có hơn 4000 ký tự, rất có thể phân tích cú pháp rằng quái vật sẽ chậm như địa ngục.
  • Kéo một danh sách ra khỏi cơ sở dữ liệu, tách nó ra và sau đó quay trở lại cơ sở dữ liệu cho một truy vấn khác về bản chất là chậm hơn một truy vấn.

TLDR: Đây là một thiết kế không hoàn hảo về cơ bản, nó sẽ không mở rộng quy mô, nó giới thiệu sự phức tạp bổ sung cho ngay cả các truy vấn đơn giản nhất và ngay lập tức nó làm chậm ứng dụng của bạn.


1
Simon, một người nào đó đã làm câu hỏi tương tự ( dba.stackexchange.com/questions/17824/iêu ), nhưng tôi không rõ tại sao cùng một FK và PK lại nằm trong cùng một bảng, điều đó làm mất 3FN.
jcho360

2
Tôi không hoàn toàn chắc chắn liệu họ có muốn có mối quan hệ nhiều-nhiều giữa Thỏa thuận và Danh mục hay một loại quyền thừa kế của Danh mục. Dù bằng cách nào, đó là một khía cạnh của điểm chính, đó là các trường được phân cách bằng dấu phẩy thay vì bảng liên kết là một ý tưởng tồi.
Simon Righarts

4

Tuy nhiên, nhóm thuê ngoài của chúng tôi đã lưu trữ nhiều danh mục trong bảng Giao dịch theo cách này:

DealId (PK) DealC Category - Tại đây, họ lưu trữ nhiều id giao dịch được phân tách bằng dấu phẩy như thế này: 18,25,32.

Đó thực sự là một thiết kế tốt nếu bạn chỉ cần truy vấn các danh mục cho một thỏa thuận nhất định.

Nhưng thật kinh khủng nếu bạn muốn biết tất cả các giao dịch trong một danh mục nhất định.

Và nó cũng làm cho nó thực sự khó khăn và dễ bị lỗi khi làm bất cứ điều gì khác - như cập nhật, đếm, tham gia, v.v.

Việc không chuẩn hóa có vị trí của nó, nhưng bạn phải nhớ rằng nó tối ưu hóa cho một loại truy vấn với chi phí của tất cả các loại khác mà bạn có thể thực hiện đối với cùng một dữ liệu. Nếu bạn biết bạn sẽ luôn truy vấn theo một mẫu, thì nó có thể mang lại cho bạn một lợi thế để sử dụng thiết kế không chuẩn hóa. Nhưng nếu có bất kỳ cơ hội nào bạn có thể cần linh hoạt hơn trong các loại truy vấn, hãy kiên trì với một thiết kế được chuẩn hóa.

Giống như bất kỳ hình thức tối ưu hóa nào khác, bạn cần biết những truy vấn nào bạn sẽ chạy trước khi bạn có thể quyết định xem việc không chuẩn hóa có hợp lý hay không.


1
Bạn có thực sự nghĩ rằng một chuỗi với ID con được phân tách bằng dấu phẩy là hữu ích không? Ý tôi là, ứng dụng phải đọc trước, sau đó phân tích ID và truy vấn tất cả các con, như thế nào select * from DealCategories where DealId in (1,2,3,4,...). Bạn có nhiều kinh nghiệm hơn, liên quan đến thiết kế cơ sở dữ liệu, hơn tôi, vì vậy có thể bạn có lý do chính đáng trong một số trường hợp cho việc "điều chỉnh cực đoan" như vậy trong các trường hợp rất cụ thể. Ý tưởng duy nhất của tôi để biện minh cho điều này là một selecttải rất cao trên Deal / DealC Category. Điều này đối với tôi rất giống với một số nhóm thuê ngoài mà không có bất kỳ kiến ​​thức thiết kế DB nào, ngoài việc tạo các bảng, đã tạo ra nó.
Erik Hart

1
@ErikHart, đây là sự không chuẩn hóa và nó thể hữu ích, nhưng quan điểm của tôi là nó phụ thuộc hoàn toàn vào các truy vấn bạn cần chạy. Bạn đúng rằng việc không chuẩn hóa làm cho tất cả các truy vấn hoạt động kém hơn, ngoại trừ một truy vấn mà nó tối ưu hóa. Nếu bạn chỉ cần chạy một truy vấn đó và bạn không quan tâm đến các truy vấn khác, thì đó là một chiến thắng. Nhưng đây là những trường hợp hiếm gặp, vì thông thường chúng tôi muốn linh hoạt truy vấn dữ liệu theo nhiều cách khác nhau.
Bill Karwin

1
@ErikHart, nếu nhóm thuê ngoài đó được cung cấp các thông số kỹ thuật của dự án chỉ bao gồm một truy vấn đối với dữ liệu này, họ có thể đã thiết kế tối ưu hóa cho chỉ truy vấn cụ thể đó. Nói cách khác, "bạn yêu cầu nó, bạn đã nhận nó." Nhưng nhà cung cấp dịch vụ gia công không có lý do để lập kế hoạch cho việc sử dụng dữ liệu trong tương lai - họ triển khai ứng dụng cho thư của những gì được viết trong thông số kỹ thuật.
Bill Karwin

1

Nhiều giá trị trong một cột chống lại hình thức bình thường thứ 1.

Nó cũng hoàn toàn không tăng tốc, vì các bảng sẽ được liên kết trong cơ sở dữ liệu. Bạn phải đọc và phân tích một chuỗi trước, sau đó chọn tất cả các danh mục cho "Giao dịch".

Việc thực hiện đúng sẽ là một bảng nối như "DealDealC loại", với DealId và DealC CategoryId.

Thực hiện phân cấp xấu?

Ngoài ra, một FK trong DealC loại cho một DealC Category khác trông giống như một triển khai xấu của hệ thống phân cấp / cây của DealC loại. Làm việc với cây thông qua mối quan hệ ID phụ huynh (còn gọi là danh sách kề) là một nỗi đau!

Kiểm tra Bộ lồng nhau (tốt để đọc, nhưng khó sửa đổi) và Bảng đóng (hiệu suất tổng thể tốt nhất, nhưng có thể sử dụng bộ nhớ cao - có thể không quá nhiều cho các loại DealC của bạn) khi thực hiện phân cấp!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.