Неправильно ли использовать несколько внешних ключей, разделенных запятыми, и если да, то почему?

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 (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
Я не был полностью уверен, хотят ли они иметь отношения «многие ко многим» между сделками и категориями или что-то вроде иерархии категорий. В любом случае, это была побочная линия к главному, что плохая идея - разделять запятыми поля вместо таблицы ссылок.
Саймон Ригартс
4

Однако наша аутсорсинговая команда сохранила несколько категорий в таблице сделок следующим образом:

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

Несколько значений в столбце соответствуют первой нормальной форме.

Это также абсолютно не увеличивает скорость, поскольку таблицы должны быть связаны в базе данных. Вы должны сначала прочитать и проанализировать строку, а затем выбрать все категории для «Сделки».

Правильная реализация будет соединительной таблицей, такой как «DealDealCategories», с DealId и DealCategoryId.

Плохая реализация иерархии?

Кроме того, FK в DealCategories другой DealCategory выглядит как плохая реализация иерархии / дерева DealCategories. Работать с деревьями через отношение Parent ID (так называемый список смежности) очень сложно!

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

Эрик Харт
источник