Следует ли добавить этот новый столбец в таблицу клиентов или в отдельную новую таблицу?

У меня есть таблица клиентов с информацией о наших клиентах (ID, логин, имя, контактная информация, разные параметры, столбец TS и т.д., ~ 15 столбцов, ~ несколько сотен клиентов).

Теперь нам нужно отправлять ежедневные обновления нашим крупнейшим клиентам (< 10% от всех клиентов). И мне нужно сохранить временную метку последнего обновления, которое было отправлено клиенту, поэтому в следующий раз я отправлю только новые обновления (я имею в виду обновления в строках порядка с TS больше, чем хранимые TS).

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

Но, как я также знаю, с точки зрения физической базы данных лучше создать новую таблицу с двумя столбцами [CustomerID, LastUpdatesSentTS], потому что только менее 10% клиентов будут иметь эту информацию TS. Я имею в виду, что если я добавлю столбец в таблицу Customers - большинство клиентов будут иметь нуль в этой колонке. Кроме того, если я создам отдельную новую таблицу, возможно, будет лучше сбросить булевский столбец "SendUpdates" из таблицы клиентов (потому что я смогу понять, какие клиенты нуждаются в обновлениях, которые нужно отправить, присоединив таблицу клиентов к новой таблице). Кроме того, в этом случае я боюсь, что через несколько лет у меня будет куча очень маленьких таблиц, когда все это может быть в таблице клиентов (без нарушения нормализации, как я понимаю).

Простыми словами, я вижу 2 возможных дизайна таблиц:

1)

Table customers:  
[CustomerID, Name, ..., SendUpdates, LastUpdatesSentTS]

2)

Table customers:  
[CustomerID, Name, ...]  
Table customer_updates_sending:
[CustomerID, LastUpdatesSentTS]

Как вы думаете?

Ответы

Ответ 1

Я бы предположил, что вы должны сделать это как вторую, отдельную таблицу.

Мы полагаем, что, по вашему мнению, только около 10% ваших клиентов нуждаются в этих "обновлениях", и поэтому примерно 90% записей из таблицы "клиенты" будут иметь поле, всегда содержащее значение NULL, если вы сделаете это как дополнительное поле в той же таблице клиентов. Внедрение этого в качестве второй таблицы позволяет избежать этой проблемы.

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

В принципе, я могу спросить себя:

"Буду ли я в любой момент в будущем, необходимо знать о история обновлений, а не только последняя?"

В зависимости от применения этого (и это похоже на бизнес-управляемую программу из того, что вы говорите), возможно, потребуется изучить историю обновлений клиентов. Think Management Information, Reporting, Ежегодные резюме и т.д.)

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

Даже если вас не интересует история обновлений клиентов, я лично предпочитаю подход с двумя таблицами, поскольку он, безусловно, позволяет вести исторический учет, и предлагает более эффективный подход (поскольку только некоторые записи из таблицы клиентов будут нужны записи во второй таблице "Обновления" ). Тем не менее, см. Мой EDIT ниже для получения дополнительной информации. Если бы я знал, что история никогда не понадобится для этих данных, я бы выполнил как одно дополнительное поле в существующей таблице клиентов.

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

EDIT:

(В ответ на комментарии к моему ответу).

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

В зависимости от характера "дополнительных" фрагментов данных необходимо принять прагматичное решение о том, как оно будет реализовано. Аарон предполагает, что в случае поля даты "LastUpdate" наличие большого количества NULL в 90% таблицы клиентов - это плохо, и я согласен с ним здесь в этом, с точки зрения NULL это не плохо. Мое собственное предложение использовать 2-х табличный подход было не столько основано на желании удалить NULL (хотя это и выполняется), а в том, чтобы гарантировать сохранение истории дат "LastUpdate" .

Конечно, если ведение истории полностью не требуется (и помните, что завтра не может потребоваться то, что сегодня не требуется), чтобы реализовать эту дату "LastUpdate" в качестве дополнительного поля в той же таблице "Клиент", будь умницей. Фактически, если бы существовала только прямая взаимно-однозначная связь между одним клиентом и одной датой "последнего обновления", разделение ее на 2 таблицы было бы неправильным. В этом случае я бы использовал его как дополнительное поле в таблице клиентов, так как теперь он является скалярным значением этого клиента.

Конечно, вы всегда можете реализовать сегодня как одно поле в таблице клиентов, а , если, это становится требованием (скажем, год вниз по строке), чтобы теперь сохранить историю "последней обновить", вы всегда можете реорганизовать свою базу данных, чтобы разделить ее на вторую таблицу, но помните, что вы собираете только исторические данные с этого момента, так как у вас не будет записей о последних датах обновления предыдущего года.

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

Ответ 2

"Когда сомневаешься, сделай простейшую вещь, которая могла бы работать" - Уорд Каннингем

Там, где я бы сказал, "добавьте вторую таблицу", но в этом случае я не считаю это оправданным. Насколько я понимаю, нет необходимости поддерживать историю значений для этого атрибута. Стол маленький. И, в конечном счете, у вас есть атрибут клиента. Конечно, не все из них будут заселены, но для меня это вторичное соображение. В большинстве случаев множество полей имеют значения NULL, но это не значит, что вы должны обязательно создать вторую таблицу для их хранения. Держите его как можно более простым (и как нормализованное), но не более простым (или нормальным-er:-). Поэтому, если бы это был я, я бы добавил эти поля в таблицу CUSTOMERS. YMMV.

Поделитесь и наслаждайтесь.

Ответ 3

Я бы пошел с опцией 2.

Мне не нравятся колонки вроде SendUpdates. ИМО, лучше иметь то, что хранится в существовании строки в другой таблице.

SELECT * FROM customer_updates_sending;

проще и быстрее, чем

SELECT * FROM  customers WHERE SendUpdates = 1;

Дальнейшие мысли в ответ на комментарий:

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

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

Я не уверен, что вы имеете в виду "вам нужно изменить исходный запрос в каждой таблице". Таблица представляет собой набор информации. Вы не сохраняете запросы в таблицах.

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

Ответ 4

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

В общем, с таким типом вопросов одно из рассмотрений, возможно, самое важное, - это типичные шаблоны использования для различных частей данных.
Догадка заключается в том, что таблица Customer в значительной степени используется для [главным образом] целей только для чтения; такое использование может поддерживаться путем сохранения обновленных (и не часто запрашиваемых) данных, таких как метка времени последнего уведомления отдельно. Будет ли информация в связанной таблице более часто включаться в запросы, тогда может быть целесообразно разместить такую ​​информацию в основной таблице.

О проблемах с редким использованием столбца временной метки обновления
(приглушенная точка, так как выбор из двух таблиц, вероятно, будет выбран, но в целом...)
Тот факт, что только 10% записей будут иметь некоторую информацию в столбце timestamp, намекает на некоторую "расточительность", мы выберем вариант 1. В действительности редкое использование этой колонки мало влияет на размер и производительность базы данных в целом. Например, если таблица легко включает столбец переменной длины, размер служебных данных размера фактически равен нулю; если это первый столбец с нулевым значением или переменной длиной, налог с минимальным размером будет взиматься, но должен иметь небольшое значение. (Также в более поздних версиях SQL-сервера, 2005, я думаю, можно использовать разреженный столбец, хотя это вряд ли стоит с тысячами или даже размером в десятки тысяч записей.)

В столбце "sendudpate" Также неплохо отказаться от булевского столбца sendupdate из основной таблицы, поместив всю связанную с обновлением информацию в соответствующую таблицу. Однако я предлагаю, чтобы тот факт, что клиент получает обновления, не должен быть скрытым для основного клиента, имеющего запись в связанной таблице. Вместо этого введите столбец "sendupdate" в соответствующем, возможно, не как простой логический, а как частотный код (например, 0 = нет обновлений, 1 = обновление ежедневно, 7 = обновление еженедельно и т.д.) Это не означает, что Я предлагаю, чтобы все клиенты должны были иметь запись в связанной таблице, но тот факт, что у них есть такая запись, является необходимым, но не достаточным условием, например, позволяющим временно отключить обновления и т.д. И т.д.

О том, подходит ли одна таблица к разрыву и правилам нормализации
Важно различать физические и логические схемы баз данных. Вполне возможно иметь физическую схему, которая не нарушает никаких правил нормализации, но не логически нормализуется. В очень широком смысле одна базовая мантра нормализации - хранить только один тип Entity на таблицу. До тех пор, пока не будет дублирования данных, можно поместить одну таблицу в одну широкую запись, фактически имеющую отношение к двум логическим объектам.
Иллюстрированный в базе данных клиентов в этом вопросе, можно решить, что физические записи будут включать дату последнего обновления, отправленного данному клиенту. Достаточно справедливо, физически не нарушено правило нормализации... Логично, однако, можно утверждать, что "Административная информация клиента" (Имя, адрес...) и "Обновить информацию" (дата последнего отправленного обновления) - это два разных объекта, даже хотя на данный момент они, похоже, находятся на уровне 1-1.
Следовательно, состояние нормализации логической модели, где сущности эффективно определены, часто находится в глазах наблюдателя, поскольку можно (иногда очень справедливо и разумно) утверждать, что элементы данных, все из которых имеют отношение 1-1, принадлежат то же понятие (сущность).


Q: Что такое логическая и физическая нормализация?... Правильно ли правила нормализации? Я попытался объяснить это выше.
"физическая нормализация" (или, скорее, нормальная форма физической схемы) рассматривает фактический состав таблиц и их отношения и применяет простые правила, чтобы знать, какая нормальная форма такая схема удовлетворяет.
"логическая нормализация" (или, скорее, нормальная форма модели данных) рассматривает эффективные сущности, найденные в системе.
Таким образом, чтобы предоставить другой пример, при проектировании простой базы данных для продажи дома, вы можете выбрать единую концепцию "ДОМА", хранящуюся в одной таблице, с такими столбцами, как "Адрес", "Кухонная комната", "Живая_Рома" и т.д.. Такая таблица будет "работать" и быть технически определенной нормальной формы; это было бы несколько непрактично, предотвращая листинг домов с двумя кухнями и т.д. В качестве альтернативы можно было бы увидеть дом как "местоположение" (адрес и, возможно, другую административную информацию) и "комнаты" (тип, поверхность, информация о полах...), в соответствии с которой каждая концепция (местоположение, комната) хранится в отдельной таблице, причем одно местоположение связано с несколькими комнатами.
Таким образом, обе эти модели могут быть помещены в физическую схему, которая является нормальной, первая модель может быть названа денормализованной (на логическом уровне) из-за того, что она не фиксирует должным образом сущности, эффективно присутствующие.


В: Я не понимаю, как вы предлагаете явно отметить тот факт, что клиент получает обновления? А:

 SELECT whatever
 FROM Customers
 JOIN NotificationTable N on N.CustomerId = C.CustomerId
 WHERE N.notificationFrequency > 0

В приведенном выше,
 - JOIN фиксирует первое условие для уведомления клиента: в таблице уведомлений должна быть соответствующая запись.
 - предикат WHERE N.notificationFrequency > 0 фиксирует очень явное условие: столбцы notificaionFrequency должны быть положительными.

Ответ 5

Я думаю, вы отлично начали понимать проблему и придумывали варианты. Оба являются разумно обоснованными проектами, которые должны работать достаточно хорошо.

Я видел, что подход №1 выходит из-под контроля - каждое новое значение конфигурации добавляется как новое поле к точке, где каждый пользователь имеет много пустых значений конфигурации, которые применяются только к нескольким населенным пунктам.

Я бы предпочел вариант 2, так как мне кажется более интуитивным методом.

Ответ 6

Мне бы хотелось, чтобы простой способ иметь ежедневный отчет о том, какие обновления я отправил. Это не другая таблица данных "клиента", а таблица дат каждому клиенту было отправлено обновление. Вопрос, который вас попросят, - "Получил ли клиент" А "их обновление в прошлый вторник?" Единственный ответ, который может предложить одностоечное решение, - "Я не знаю, но они получили его в пятницу". Может быть неприемлемым. Без истории вы не можете повторно отправить обновление, которое клиент не получил.

Ответ 7

@CraigTP, то снова, на ваш второй пункт тоже. есть намного лучшие и более полные способы ведения истории записей. Что касается этого, это можно сделать (и должно быть сделано) по полной таблице клиентов. Это в основном называется Аудит. У вас есть триггеры для этого. У вас довольно простые подходы в спящем режиме, чтобы сделать это из коробки для этого. Наконец, я хотел бы добавить новый столбец в существующий дизайн таблицы

EDIT Я вернулся к этому и подумал, что я тоже пропустил часть SendUpdate. ИМХО, эти столбцы также не оправданы (в любой таблице как таковой) Если вы это сохраните, это транзитивная зависимость, которую вы должны нормализовать в 3NF. Но в любом случае я считаю, что сохранение истории и сохранение расширения, добавление нового столбца - это путь без столбца SendUpdate.