Статьи

Математика и SQL Часть 6: проблема с NULL

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

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

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

Определение нулевого множества

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

Null as Unknown

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

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

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

NULL как неизвестный создает проблемы для логики предикатов, потому что он вводит логику с тремя значениями (истина, ложь и неизвестность), но это, как правило, только проблемы, когда один хранит значение (в отличие от ссылки, такой как ключ) в таблице. 1 + NULL — это NULL. NULL ИЛИ ЛОЖЬ — NULL. NULL ИСТИНА — ИСТИНА. Это усложняет ситуацию. Но иногда мы должны ….

Нуль как не применимо

Одним из часто встречающихся серьезных антипаттернов является использование NULL для обозначения «Не применимо» или «Не имеет значения». Есть несколько типов данных, которые не имеют естественных пустых / неоперативных типов. Главными среди них являются числовые типы. Хуже того, Oracle рассматривает NULL как то же значение, что и пустая строка для типов VARCHAR.

Теперь очевидная проблема здесь заключается в том, что база данных здесь не знает, что NULL не является неизвестным, и поэтому вам придется отслеживать это самостоятельно, использовать функции COALESCE () для преобразования в нормальные значения и т. Д. В общем, если вы Вы можете избежать использования NULL в значении «Не применимо».

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

CREATE TABLE wage_class (
   id int not null,
   label text not null
);

INSERT INTO wage_class VALUES(1, 'salary'), (2, 'hourly');

CREATE TABLE wage (
   ssn text not null,
   emp_id int not null,
   wage_class int not null references wage_class(id),
   hourly_wage numeric,
   salary numeric,
   check (wage_class = 1 or salary is null),
   check (wage_class = 2 or hourly_wage is null)
);

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

Что Nulls Делают, чтобы Предсказать Логику

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

WHERE ssn like '1234%' AND salary < 50000

против

WHERE ssn like '1234%' AND salary < 50000 IS NOT FALSE

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

Нули и Ограничения

Несмотря на все проблемы, NULL стали чем-то вроде необходимого зла. Ограничения — большая часть причины.

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

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

Нули и иностранные ключи

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

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

Как должны выглядеть нули: NULL, NOVALUE, UNKNOWN

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