Чи неправильне використання декількох іноземних ключів, розділених комами, і якщо так, то чому?


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
вогонь, який негайно передав команду, перш ніж вони завдадуть більше шкоди ... (-_-)
Рафа,

Відповіді:


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, тоді ви, у своєму коді заявки, повинні пройти кожну угоду, шукаючи посилання на категорію №26 та видалити їх. Це щось, з чим слід звертатися на рівні даних, і доводиться обробляти це у вашому додатку - дуже погана річ .
  • Знову сукупні запити ( COUNTі SUMт. Д.) Знову різняться від "складних" до "майже неможливих". Запитайте своїх розробників, як вони отримають вам список усіх категорій з урахуванням кількості угод у цій категорії. При правильному дизайні це чотири рядки SQL.
  • Оновлення стає набагато складніше (тобто у вас є угода, яка складається з п'яти категорій, але ви хочете видалити дві та додати ще три). Це три рядки SQL з належним дизайном.
  • Врешті-решт ви зіткнетесь із VARCHARобмеженнями довжини списку. Хоча якщо у вас є список, відокремлений комами, налічує понад 4000 символів, ймовірність аналізувати, що монстр все одно буде повільним, як пекло.
  • Витягнення списку з бази даних, розділення його та повернення до бази даних для іншого запиту суттєво повільніше, ніж один запит.

TLDR: Це принципово недосконала конструкція, вона не буде масштабуватись, вона додає додаткових складностей навіть найпростішим запитам, і прямо поза коробкою вона уповільнює вашу програму.


1
Саймон, хтось робив те саме питання ( dba.stackexchange.com/questions/17824/… ), але я не знаю, чому ті ж FK і PK в одній таблиці, що гальмує 3FN.
jcho360

2
Я не був повністю впевнений, чи хочуть вони мати багато-багато-багато стосунків між угодами та категоріями, чи якась спадковість категорій. Так чи інакше, основним моментом було відхилення від точки, що обмеження комами замість таблиці посилань є поганою ідеєю.
Simon Righarts

4

Однак наша команда з аутсорсингу зберегла кілька категорій у таблиці Deal таким чином:

DealId (PK) DealCategory - Тут зберігаються кілька ідентифікаторів угод, розділених такими комами: 18,25,32.

Це насправді гарний дизайн, якщо вам потрібно лише запитувати категорії щодо певної угоди.

Але це жахливо, якщо ви хочете знати всі угоди в даній категорії.

А також робить насправді важким і схильним до помилок будь-що інше - як оновлення, підрахунки, приєднання тощо.

Денормалізація має своє місце, але ви повинні пам’ятати, що вона оптимізується для одного типу запиту за рахунок усіх інших, які ви можете зробити проти одних і тих же даних. Якщо ви знаєте, що ви завжди будете запитувати в одному шаблоні, то це може дати вам перевагу використовувати денормалізований дизайн. Але якщо є якийсь шанс, вам може знадобитися більша гнучкість у типах запитів, дотримуйтесь нормалізованого дизайну.

Як і будь-яка інша форма оптимізації, вам потрібно знати, які запити ви збираєтеся виконувати, перш ніж ви зможете вирішити, чи виправдана денормалізація.


1
Ви дійсно вважаєте, що рядок із розділеними комами дочірніми ідентифікаторами корисний? Я маю на увазі, що додаток потрібно було прочитати спочатку, потім розібрати ідентифікатори та запитати всіх дітей, як select * from DealCategories where DealId in (1,2,3,4,...). Ви маєте більше досвіду щодо дизайну баз даних, ніж я, тому, можливо, у вас є вагомі причини в деяких випадках для такої "екстремальної настройки" у дуже конкретних випадках. Моя єдина ідея виправдати це - дуже велике selectнавантаження на Deal / DealCategory. Мені це схоже на те, що якась команда з аутсорсингу без будь-яких знань з проектування БД, крім створення таблиць, створила це.
Ерік Харт

1
@ErikHart, це денормалізація, і це може бути корисно, але я можу сказати, що це повністю залежить від запитів, які потрібно запустити. Ви праві, що денормалізація робить всі запити гіршими, за винятком одного запиту, для якого він оптимізований. Якщо вам потрібно запустити лише один запит, а інші запити вам не хвилюються, це виграш. Але це рідкісні випадки, оскільки, як правило, ми хочемо гнучкості запитувати дані різними способами.
Білл Карвін

1
@ErikHart, якби цій аутсорсинговій команді були надані специфікації проекту, які включали лише один запит щодо цих даних, вони могли б розробити оптимізацію лише для цього конкретного запиту. Іншими словами, "ви просили його, ви отримали". Але у постачальника аутсорсингу немає підстав планувати майбутнє використання даних - вони реалізують додаток до листа того, що написано у специфікації.
Білл Карвін

1

Кілька значень у стовпці проти 1-ї нормальної форми.

Це також абсолютно не збільшує швидкість, оскільки таблиці повинні бути пов'язані в базі даних. Спочатку потрібно прочитати та проаналізувати рядок, а потім вибрати всі категорії для "Угоди".

Правильною реалізацією буде таблиця з'єднання типу "DealDealCategories", з DealId і DealCategoryId.

Погана реалізація ієрархії?

Також FK в DealCategories до іншої DealCategory виглядає як погана реалізація ієрархії / дерева DealCategories. Робота з деревами через батьківський ідентифікатор (так званий список суміжності) - це біль!

Перевірте наявність вкладених наборів (добре читати, але важко модифікувати) та таблиці закриття (найкраща загальна продуктивність, але можливо високе використання пам'яті - можливо, не надто для ваших категорій DealCategories) при впровадженні ієрархій!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.