Проект нормализации базы данных - одна или несколько таблиц

Должно ли это быть представлено в базе данных как 1 таблица или 3 таблицы? У меня и моего друга разные мнения на этот счет, поэтому я хотел бы увидеть общие взгляды на это. (Может быть, это должно быть голосование за любое решение?)

Create Table Order
// Basic fields of the table
 - ID (Primary key)
 - CustomerID  (integer, with a FK)
 - Quantity
 - ProductID  (integer, with a FK)

 // Then depending on user selection, either these fields need to be specified 
 // (could be factored out to a separate table):
 {
 - InternalAccountID (integer, with a FK)
 - InternalCompanyID (integer, with a FK)
 }

 // Or these (could be factored out to a separate table):
 {
 - ExternalAccountNumber (free text string)
 - ExternalCompanyName (free text string)
 - ExtraInformation (free text string)
 }

1 стол подход:

Плюсы:

  • производительность (одна вставка в отличие от двух, проверка FK, нет соединений)
  • вероятно, занимает меньше места (дополнительные таблицы имеют накладные расходы + индексы + дополнительное поле идентификатора)
  • один стол в отличие от трех
  • вряд ли оправданно разделять на новые таблицы только для 2+3 полей (или что?)

Минусы:

  • Обнуляемые поля
  • Потенциально дополнительный столбец типа (можно пропустить)
  • Перерывы 3NF (?)

Плюсы и минусы любезно запрашиваются, а также личные мнения.:)

РЕДАКТИРОВАТЬ: я попытался упростить пример, используя другие объекты, чем я на самом деле, поэтому любые предложения по изменению модели не помогут мне. Т.е. сфокусируйтесь на технических аспектах больше, чем на предметной модели, пожалуйста.

7 ответов

Решение

Мое мнение таково, что если

 // Then depending on user selection, either these fields need to be specified 
 // (could be factored out to a separate table):
 {
 - InternalAccountID (integer, with a FK)
 - InternalCompanyID (integer, with a FK)
 }

 // Or these (could be factored out to a separate table):
 {
 - ExternalAccountNumber (free text string)
 - ExternalCompanyName (free text string)
 - ExtraInformation (free text string)
 }

всегда 1:1 с заказом (т. е. вы не можете иметь 3 идентификатора аккаунта), затем оставьте его как одну таблицу. Чтобы решить проблему с нулевым значением, вы можете добавить еще один столбец с именем InternalCustomer (логическое значение) или CustomerType (varChar), который можно использовать для определения внутреннего или внешнего клиента, чтобы узнать, какой из двух наборов полей следует искать для конкретный клиент.

Поскольку мы не знаем полного использования этих данных или схемы для всей БД, любой ответ на этот вопрос не может быть полностью квалифицированным.

Надеюсь, это говорит само за себя.

order_model_v1

Я не пурист, так что 3nf хорош, когда это имеет смысл... но вам не нужно принимать это как должное, что так будет всегда.

С прагматической точки зрения, какова ваша цель? Ваш список плюсов / минусов - хорошее начало. Я бы добавил еще несколько идей в список - как вы считаете нужным.

1) Должна ли какая-либо другая таблица в вашей базе данных соотноситься (например, объединяться) с этими данными? Это своего рода точка зрения RDB.

2) Будет ли расти ваша база данных? Даже если 1 таблица имеет смысл сейчас, будет ли она иметь смысл? Вы пожалеете об этом, если обнаружите, что хотите добавить больше таблиц, и ваша ненормализованная таблица вынуждает вас "обойти" ее, обрабатывая возвращаемые дополнительные строки, более медленное время выполнения и т. Д.

3) Что происходит, когда ваш клиент получает новую внешнюю учетную запись, или что у вас есть. Будете ли вы создавать новую запись? Как вы будете отвечать на такие вопросы, как "Какой у клиента такой-то номер счета?".

...

Я думаю, в общем, я иду на масштабируемость, что в данном случае может означать 3nf. С 1 таблицей легче работать в очень узкой области, но если что-то изменится, вы будете иметь дело с тем, "Как разделить эту таблицу на правильно связанные 3nf-таблицы, не путая все зависимости, которые были созданы на Это?". Это не весело.

Связана ли информация учетной записи с клиентом до того, как он сможет сделать заказ (т. Е. У вас есть еще одна таблица, в которой вы отслеживаете, какие идентификаторы учетной записи может использовать данный CustomerID)? Можете ли вы абстрагировать все учетные записи в достаточно однородную схему (которая может иметь несколько нулей), если у вас есть один универсальный AccountId (суррогатный ключ), а затем в таблице Account есть 3 поля varchar и одно, которое отслеживает тип учетной записи (используется для выставления счетов и т. д.)

Если вы можете сделать это, тогда ваш заказ отслеживает только один AccountID, так как заказ (как организация) действительно не заботится о том, какой метод оплаты был использован - он заботится только о том, что это законный / существующий / одобренный AccountId для этого пользователя. Все остальное - это, так сказать, чужое дело (выставление счетов или проверка средств и т. Д.), И этому подразделению и его обработке все равно потребуется больше данных.

Это делает ваш заказ чистым и без нуля, а также облегчает разделение проблем.

Концептуально ваш Орден на самом деле представляет собой так называемую таблицу фактов, содержащую только цифры и FK-ы, небольшие по размеру, но с огромным количеством из них.

Так:

 Table Order (
     - OrderId
     - Quantity
     - ProductId
     - DiscountId -- sonner or latter :-)
     - AccountId
     - PaymentStatus -- probaly FK as well or predefined constant
 )

 Table Account (
     - AccountId
     - BillingInfo  -- akka ext acct number as text
     - PrincialName -- akka ext company name, some equivalent for internal acct-s
     - AdditionalData
 )

Если вы хотите избежать дублирования данных, вы должны использовать решение с 2 или 3 таблицами. Например, если у вас есть External столбцы в таблице Order, значение может существовать несколько раз. Если данные выглядят так:

ID   ExternalCompanyName
1    ACME
2    ACME
3    My Company
4    ACME

Теперь, если ACME меняет имена на ACME, Inc., вы должны обновить много строк. Если таблицы нормализованы, когда внешние компании находятся в отдельной таблице, вы обновите одну строку. Обратите внимание, что может быть аргумент для помещения номера счета в его собственную таблицу, но мы оставим это для крайней нормализации.

Похоже, что это не отношение "один к одному" между заказом и компанией / счетом, если только у каждой компании / счета не может быть только один заказ. это больше похоже на отношения 1-ко-многим.

Теперь, что произойдет, если при обновлении ExternalCompanyName в среде с одной таблицей будет допущена ошибка, и обновятся только некоторые строки. У вас есть несколько строк с ACME и несколько строк с ACME, Inc. В результате вы столкнулись с ситуацией с неверными данными.

Кроме того, если это действительно отношение "один ко многим", вы действительно не экономите место. Вы дублируете данные в порядке, а не сохраняете их один раз в другой таблице.

Я бы абсолютно не пошел с решением с 3 столами. Разбивая эти данные на 3 таблицы, вы не сможете получить ни одного запроса, возвращающего полный заголовок заказа, без объединения с внешним ключом, и каждая вставка нового заказа обновляет несколько таблиц и индексов, что является проблемой для параллелизма. Я бы предложил использовать 2 таблицы, одну для InternalOrders и одну для ExternalOrders. Для тех случаев, когда вам нужен консолидированный запрос данных из обоих наборов заказов, определите представление, которое является объединением обеих таблиц.

Я удивлен, увидев идентификатор продукта и количество как часть заголовка заказа. Каждая база данных отслеживания заказов, которую я когда-либо видел, разбивает позиции заказа на отдельные таблицы, используя идентификатор заказа в качестве внешнего ключа, так что в один заказ может входить несколько продуктов (или один и тот же продукт с различными количествами, сроками доставки и т. Д.).).

По мере увеличения громкости выбор из двух таблиц может быть намного быстрее, чем одной. Иногда этот вид рефакторинга (раздела) выполняется на зрелых базах данных для повышения производительности.

Представьте себе, что вы используете это для объединения нескольких таблиц, где некоторые критерии находятся в этой таблице, а другие - в разных таблицах.

select from order join customer using (customer_id)
where
    order.order_date between ? and ?
    and customer.name = ?

Это может в конечном итоге получить все order строки для дат с диска, а затем выбрасываем многие из них, потому что они не соответствуют соединению. Этот выбор с диска должен быть медленным и может испортить ваш кэш RAM.

select from order join order_detail using (order_id) join customer using (customer_id)
where
    order.order_date between ? and ?
    and customer.name = ?

В этом случае, когда он загружает все order строк с диска не будет так больно, как раньше, потому что таблица уже и меньше. Не нужно загружать все длинные поля, которые не имеют отношения к фильтрации. В конце концов, после присоединения к customer, только те order_detail строки, которые соответствуют всем критериям.

Если вы ожидаете, что оно будет большим, вам следует рассмотреть возможность разделения таблицы таким образом, чтобы поля, наиболее важные для поиска, находились в одной таблице, а поля "данные" - в других таблицах "один к одному".

Суть в следующем: нормальная форма и домен - это одно, но производительность часто требует компромиссов. Вы можете скрыть некоторые из них (покрыть разделение видом), но не все (дублировать / агрегировать поля для более быстрого выбора).

Другие вопросы по тегам