Почему мы не должны допустить значения NULL?

125

Я помню, как читал эту статью о дизайне базы данных, и я также помню, что сказано, что у вас должны быть свойства поля NOT NULL. Я не помню, почему это было так.

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

Но что вы делаете в случае дат, даты и времени (SQL Server 2008)? Вы должны будете использовать какую-нибудь историческую дату или дату с дном.

Есть идеи по этому поводу?

Томас Стрингер
источник
4
Этот ответ дает представление об использовании NULL. Dba.stackexchange.com/questions/5176/…
Дерек Дауни,
10
В самом деле? Почему СУБД вообще позволяет нам использовать NULL, если мы не должны их использовать? В NULL нет ничего плохого, если вы знаете, как с ними обращаться.
Fr0zenFyr
3
Было ли это моделирование данных BI? Как правило, вы не должны допускать пустые значения в таблицах фактов ... в противном случае пустые значения являются вашими друзьями при правильном использовании. =)
Сэм Йи
2
@ Fr0zenFyr, просто потому, что СУБД позволяет нам что-то делать, это не обязательно хорошая идея. Ничто не заставляет нас объявлять первичный ключ или уникальный ключ в таблице, но мы, за редким исключением, делаем это.
Леннарт
3
Я думаю, что полное рассмотрение этого вопроса должно было бы сделать ссылку на первоначальное требование Кодда о том, что СУБД должна иметь систематический способ обработки недостающих данных. В реальном мире бывают ситуации, когда создается местоположение для данных, но нет данных, которые можно в него поместить. Архитектор данных должен дать ответ на этот вопрос, будь то проектирование базы данных, разработка приложений или и то, и другое. SQL NULL менее чем идеален в удовлетворении этого требования, но лучше, чем ничего вообще.
Уолтер Митти

Ответы:

230

Я думаю, что вопрос плохо сформулирован, поскольку формулировка подразумевает, что вы уже решили, что значения NULL плохие. Возможно, вы имели в виду "Должны ли мы разрешить NULL?"

В любом случае, вот мое мнение: я думаю, что NULL - это хорошая вещь. Когда вы начинаете предотвращать NULL только потому, что «NULL плохие» или «NULL трудные», вы начинаете создавать данные. Например, что если вы не знаете дату моего рождения? Что вы собираетесь поместить в колонку, пока не узнаете? Если вы что-то похожи на множество людей, отличных от NULL, вы собираетесь ввести 1900-01-01. Теперь меня поместят в гериатрическую палату и, вероятно, мне позвонят из местной местной новостной станции, поздравив меня с долгой жизнью, спросив мои секреты такой долгой жизни и т. Д.

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

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

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

Не бойтесь NULL, но будьте готовы узнать или диктовать, когда и где они должны использоваться, а когда и где они не должны.

Аарон Бертран
источник
3
«какое-то произвольное значение токена для обозначения того факта, что оно неизвестно», это называется « дозорным значением»
Александр
4
Но что мешает вам создать отдельную таблицу, в birth_dateкоторой вы храните даты рождения? Если дата рождения неизвестна, просто не вводите дату рождения в birth_date. Нули - это катастрофа.
Эльдар Агаларов
6
@EldarAgalarov Это звучит как рассуждение Трампа («катастрофа» почему? Как? Для кого? Ваше мнение, что что-то является «катастрофой», не делает это так). В любом случае, дата рождения - это только один пример. Если у вас есть сотрудники, члены или клиенты, у которых есть 15 столбцов, которые могут быть обнулены, собираетесь ли вы создать 15 дополнительных таблиц? Что делать, если у вас есть 50? Что если в вашей таблице фактов DW 500? Техническое обслуживание для предотвращения попадания в вашу базу данных больших, страшных и пустых значений становится в 10 раз хуже, чем любая «катастрофа», которой вы боитесь ...
Аарон Бертран
3
@AaronBertrand, если ваша таблица имеет 15 потенциально обнуляемых столбцов, она пахнет очень плохо ^^ Не то, чтобы огромное количество столбцов изначально было плохим, но это может указывать на плохой дизайн ИЛИ требуемую денормализацию. Но это вызовет вопросы.
programaths
2
@Wildcard Итак, вы никогда не видели, чтобы люди хранили, 1900-01-01чтобы избежать значения даты и времени NULL? Хорошо, тогда. Кроме того, NULL = неизвестно и неизвестно = ложно. Я не уверен, какие проблемы это может вызвать, кроме того, что люди не рождаются, зная об этом (как будто они не рождаются, зная много вещей, присущих сложной СУБД). Опять махаем руками и говорим "Проблема! Бедствие!" не делает это так.
Аарон Бертран
57

Установленные причины:

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

  • NULL нарушает двухзначную (знакомую True или False) логику и требует трехзначную логику. Это гораздо сложнее даже правильно реализовать, и, конечно, плохо понимают большинство администраторов баз данных и почти все не администраторы баз данных. Как следствие, это положительно вызывает много тонких ошибок в приложении.

  • Смысловое значение какой - либо конкретной NULL остается приложением , в отличие от фактических значений.

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

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

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

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

Фабиан Паскаль отвечает на ряд аргументов в «Null Nullified» .

большой нос
источник
3
Ваша ссылка на «Как обрабатывать недостающую информацию без нуля» прекрасно показывает, почему мы не можем обойтись без нуля: некоторые из предложений было бы невозможно рациональным образом реализовать на основных РСУБД в их нынешнем виде.
Джек Дуглас
7
Джек: Правильно, но «текущие реализации не могут этого сделать» не является аргументом для статус-кво :-)
bignose
17
Это как сказать, что мы не должны летать, потому что самолеты не идеальны?
Аарон Бертран
11
Нет, это говорит о том, что поставщики должны прекратить ссылаться на оправдания для нулей, которые могли быть действительными сорок лет назад, но уже давно пережили свой разумный срок хранения. Время ввода-вывода больше не составляет порядка 80 мс. Одиночные циклы ЦП уже не в порядке микросекунд. Пределы памяти больше не составляют порядка нескольких мегабайт. В отличие от сорока лет назад, аппаратные скорости и емкости, необходимые для работы без нулевых значений, в настоящее время действительно существуют, а стоимость не является чрезмерно высокой. Он говорит, что пришло время двигаться дальше.
Эрвин Смут
2
Ссылка "NULL путаница" не работает.
jpmc26
32

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

IIRC, Кодд предположил, что текущая реализация нуля (то есть отсутствует / отсутствует) может быть улучшена за счет наличия двух нулевых маркеров, а не одного: «не присутствует, но применимо» и «не присутствует и не применимо». Не могу представить, как это улучшит реляционный дизайн.

Марк Стори-Смит
источник
2
Я предлагаю иметь пользовательский набор различных видов nullи пользовательскую многозначную логику, чтобы идти с ними: p
Джек Дуглас
13
Это не единственные варианты. Вы исключаете альтернативу нормализации: вместо столбцов, которые могут иметь или не иметь значение, используйте другую таблицу, которая может иметь или не иметь соответствующую строку для первой таблицы. Значение наличия или отсутствия строки влечет за собой значение таблиц, и нет специального регистра значений NULL или часового и т. Д.
bignose
7
Присутствие NULL не требует специальных значений в регистре или страже. Это всего лишь симптомы того, как некоторые люди решают иметь дело с NULL.
Аарон Бертран
Стоит отметить, что '' отличается от нуля в PostgreSQL (хотя и не в Oracle) и поэтому дает вам двукратный маркер, и вы можете использовать 0 для числовых столбцов. Проблема с 0 заключается в том, что он не работает для внешних ключей.
Крис Треверс
13

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

  1. Нулевые значения делают разработку более сложной и подверженной ошибкам.
  2. Нулевые значения делают запросы, хранимые процедуры и представления более сложными и подверженными ошибкам.
  3. Нулевые значения занимают место (? Байты на основе фиксированной длины столбца или 2 байта для переменной длины столбца).
  4. Нулевые значения могут и часто влияют на индексирование и математику.

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

Во-первых, я хочу установить что-то прямое для всех будущих читателей ... Значения NULL представляют неизвестные данные, а не неиспользуемые данные. Если у вас есть таблица сотрудников, которая имеет поле даты увольнения. Нулевое значение в дате прекращения действия объясняется тем, что это поле является обязательным в будущем, которое в настоящее время неизвестно. Каждому сотруднику, активному или уволенному, в определенный момент будет добавлена ​​дата в это поле. Это, на мой взгляд, единственная причина для поля Nullable.

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

Учитывая приведенную выше информацию, лучший способ справиться с обнуляемыми данными, которые БУДУТ использоваться, - это разрешить обнуляемые значения. Это печально, но это правда, и ваши разработчики будут ненавидеть вас за это. Второй тип обнуляемых данных должен быть помещен в связанную таблицу (IE: учетная запись, учетные данные и т. Д.) И иметь отношение «один к одному». Это позволяет пользователю существовать без учетных данных, если они не нужны. Это устраняет дополнительный риск безопасности, ценное пространство базы данных и обеспечивает намного более чистую базу данных.

Ниже приведена очень упрощенная структура таблицы, показывающая как обязательный обнуляемый столбец, так и отношение «один к одному».

Неизвестные Nullable и отношения один-к-одному

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

Николас Агирре
источник
2
Я бы просто изменил его так, чтобы TerminationDateв записях сотрудников не было, но имелась таблица, для TerminatedEmployeeкоторой сотрудники перемещаются (не копируются) приложением после их увольнения. Очевидно, что это хорошо работает с таблицей Account, потому что в таблице не будет связанной учетной записи TerminatedEmployee. Если вам все еще нужны телефонные номера, я бы поменял внешние ключи, чтобы таблицы сотрудников и уволенных сотрудников имели идентификатор номера телефона, а не наоборот.
Programster
2
Я мог бы буквально несколько дней говорить о том, почему это будет плохо. Избыточные таблицы, плохие практики SQL, поэтому разработчикам придется искать в двух местах данные о сотрудниках, проблемы с отчетами, проблемы с прямыми URI для сотрудника, который не существует (был перемещен), и этот список можно продолжить и вкл. Вполне нормально иметь NULLS для полей, которые когда-нибудь будут иметь значение, это другая история, когда есть поля, которые никогда не заполняются и никогда не используются. Ряд потенциальных проблем и обходных путей для этой работы не стоил бы небольшого вопроса проверки NULL на поле.
Николас Агирре
1
Я не согласен. Единственное лишнее - это пустое поле для даты прекращения, которое может никогда не заполниться. Разработчики должны искать в соответствующей таблице только те данные, которые им нужны, и могут повысить производительность. Если по какой-либо причине вам нужны как уволенные, так и не уволенные сотрудники, это разрешается объединением, но 90% времени ваше приложение, вероятно, будет хотеть того или другого. Я думаю, что указанная мной схема лучше, потому что было бы невозможно назначить дату увольнения сотрудника, и у него все еще была бы учетная запись.
Programster
2
Я не сказал избыточные данные, я сказал избыточные таблицы. Кроме того, любое изменение таблиц сотрудников должно переходить к завершенным таблицам; это делает приложение подверженным ошибкам и делает работу разработчика намного более сложной. Кроме того, поле даты прекращения будет заполнено почти для всех. Создание второй идентичной структуры таблицы, а также перемещение данных является расточительным и проблематичным. Не включать тестирование каждый раз, чтобы убедиться, что данные таблицы были перемещены и очищены. Это плохая практика, чтобы удалить данные из таблицы, даже если только переместить их. Если вы так озабочены одним полем, что ...
Николас Агирре
1
... это почти всегда будет заполнено во времени, а затем создайте завершенную таблицу с отношением 1 к 1 обратно на сотрудника. Я работаю с различными базами данных весь день как в качестве администратора баз данных, так и в качестве разработчика, и я рад, что мне еще не приходилось сталкиваться с такой структурой, которую вы предложили. Особенно с точки зрения разработчика, было бы кошмаром писать и проверять все ошибки, потому что вы не знаете, из какой таблицы это происходит. Даже при написании объединения у данных, возвращаемых в программное обеспечение, будет поле с нулевыми данными, которое все равно потребует от вас также проверить это.
Николас Агирре
13

Помимо всех проблем с NULL, сбивающих с толку разработчиков, у NULL есть еще один очень серьезный недостаток: производительность

Пустые столбцы - это катастрофа с точки зрения производительности. Рассмотрим целочисленную арифметику в качестве примера. В нормальном мире без NULL «легко» векторизовать целочисленную арифметику в коде ядра СУБД с использованием инструкций SIMD для выполнения практически любых вычислений на скоростях, превышающих 1 строку на цикл ЦП. Однако, как только вы вводите NULL, вам нужно обработать все особые случаи, которые создает NULL. Современные наборы команд ЦП (читай: x86 / x64 / ARM и логика GPU) просто не оснащены для эффективной работы.

Рассмотрим разделение в качестве примера. На очень высоком уровне это логика, которая вам нужна с ненулевым целым числом:

if (b == 0)
  do something when dividing by error
else
  return a / b

С NULL это становится немного сложнее. Вместе с bвами понадобится индикатор, если bон нулевой и аналогично для a. Чек теперь становится:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b

Арифметика NULL значительно медленнее работает на современном процессоре, чем ненулевая арифметика (в 2-3 раза).

Становится хуже, когда вы вводите SIMD. С SIMD современный процессор Intel может выполнять 4 x 32-разрядные целочисленные деления в одной инструкции, например:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

Теперь есть способы обработки NULL и в SIMD, но это требует использования большего количества векторов и регистров ЦП и некоторой умной битовой маскировки. Даже при хороших уловках снижение производительности целочисленной арифметики NULL возрастает в 5-10 раз медленнее даже для относительно простых выражений.

Нечто подобное выше относится к агрегатам и, в некоторой степени, к объединениям.

Другими словами: существование NULL в SQL - это несоответствие импеданса между теорией баз данных и фактическим дизайном современных компьютеров. Существует довольно веская причина, по которой NULL сбивает с толку разработчиков - поскольку целое число не может быть NULL в большинстве вменяемых языков программирования - это просто не то, как работают компьютеры.

Томас Кейсер
источник
10

Интересные вопросы.

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

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

Но что вы делаете в случае дат, даты и времени (SQL Server 2008)? Вы должны будете использовать какую-нибудь историческую дату или дату с дном.

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

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

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

Крис Траверс
источник
10

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

Просто знайте, как ваша СУБД обрабатывает их в операциях SELECT, таких как математика, а также в индексах.

Дерек Дауни
источник
-12

Вау, правильный ответ «Не допускайте значений NULL, если это не нужно, потому что они ухудшают производительность», так или иначе, является последним оцененным ответом. Я буду высказывать это и уточнять. Когда СУБД допускает значения NULL для не разреженного столбца, этот столбец добавляется в растровое изображение, которое отслеживает, является ли значение NULL для каждой отдельной строки. Таким образом, добавляя возможность NULL к столбцу в таблице, где все столбцы не допускают значения NULL, вы увеличиваете объем памяти, необходимый для сохранения таблицы. Кроме того, вы требуете от СУБД чтения и записи в растровое изображение, что снижает производительность всех операций.

Кроме того, в ряде случаев разрешение NULL будет нарушать 3NF. Хотя я не сторонник 3NF, как многие мои коллеги, рассмотрим следующий сценарий:

В таблице Person есть столбец с именем DateOfDeath, который можно обнулять. Если человек умер, он будет заполнен их DateOfDeath, в противном случае он будет пустым. Существует также необнуляемый битовый столбец, называемый IsAlive. Этот столбец имеет значение 1, если человек жив, и 0, если человек мертв. Подавляющее большинство хранимых процедур использует столбец IsAlive, они заботятся только о том, жив ли человек, а не о его DateOfDeath.

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

Полезный сценарий T-SQL для поиска пустых столбцов без NULL во всех схемах:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;

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

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

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

Также обратите внимание, что это только для настоящих СУБД, и я основываю техническую часть своих ответов на SQL Server. Перечисленный T-SQL для поиска пустых столбцов без нулей также взят из SQL Server.

Мэтью Сонтум
источник
1
Комментарии не для расширенного обсуждения; этот разговор был перемещен в чат .
Пол Уайт