Варианты устранения NULLable столбцов из модели БД (чтобы избежать трехзначной логики SQL)?

некоторое время назад, я читал книги SQL и реляционная теория на дейт. Автор хорошо известен критикой трехзначной логики SQL (3VL).1)

автор делает некоторые сильные моменты о том, почему 3VL следует избегать в SQL, однако он не обрисовывает как выглядела бы модель базы данных, если бы не разрешались столбцы с нулевыми значениями. Я думал об этом некоторое время. и придумали такие решения. Если я пропустил другие варианты дизайна, я хотел бы услышать о них!

1) критика даты 3VL SQL, в свою очередь, также подверглась критике: см. эта статья Клода Rubinson (включает в себя оригинальную критику C. J. Date).


пример таблицы:

в качестве примера возьмем следующую таблицу, где у нас одна столбец с нулевым значением (DateOfBirth):

#  +-------------------------------------------+
#  |                   People                  |
#  +------------+--------------+---------------+
#  |  PersonID  |  Name        |  DateOfBirth  |
#  +============+--------------+---------------+
#  |  1         |  Banana Man  |  NULL         |
#  +------------+--------------+---------------+

Вариант 1: Эмуляция NULL через флаг и значение по умолчанию:

вместо того, чтобы сделать столбец допускает значение null, значение по умолчанию (например,1900-01-01). Дополнительный BOOLEAN столбец будет указывать, будет ли значение в DateOfBirth надо просто игнорировать или действительно ли он содержит данные.

#  +------------------------------------------------------------------+
#  |                              People'                             |
#  +------------+--------------+----------------------+---------------+
#  |  PersonID  |  Name        |  IsDateOfBirthKnown  |  DateOfBirth  |
#  +============+--------------+----------------------+---------------+
#  |  1         |  Banana Man  |  FALSE               |  1900-01-01   |
#  +------------+--------------+----------------------+---------------+

Вариант 2: превращение столбца с нулевым значением в отдельный таблица:

столбец nullable заменяется новой таблицей (DatesOfBirth). Если в записи нет данных для этого столбца, в новой таблице не будет записи:

#  +---------------------------+ 1    0..1 +----------------------------+
#  |         People'           | <-------> |         DatesOfBirth       |
#  +------------+--------------+           +------------+---------------+
#  |  PersonID  |  Name        |           |  PersonID  |  DateOfBirth  |
#  +============+--------------+           +============+---------------+
#  |  1         |  Banana Man  |
#  +------------+--------------+

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


вопрос: Есть ли другие варианты устранения NULL (и если да, то каковы они)?

7 ответов


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

его решение является вариантом вашего второго подхода. Это шестая нормальная форма, с таблицами для хранения даты рождения и идентификаторов, где она неизвестна:

#  +-----------------------------+ 1    0..1 +----------------------------+
#  |         People'             | <-------> |         DatesOfBirth       |
#  +------------+----------------+           +------------+---------------+
#  |  PersonID  |  Name          |           |  PersonID  |  DateOfBirth  |
#  +============+----------------+           +============+---------------+
#  |  1         |  Banana Man    |           ! 2          | 20-MAY-1991   |
#  |  2         |  Satsuma Girl  |           +------------+---------------+
#  +------------+----------------+
#                                  1    0..1 +------------+
#                                  <-------> | DobUnknown |
#                                            +------------+
#                                            |  PersonID  |
#                                            +============+
#                                            | 1          |
#                                            +------------+

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

конечно, это несколько теоретические. Состояние SQL в эти дни все еще недостаточно развито, чтобы справиться со всем этим. Эти недостатки освещаются в выступлении Хью. Одна вещь, которую он упоминает, не совсем правильна: некоторые ароматы SQL поддерживают несколько назначений-например Oracle вставить весь синтаксис.


Я не читал, но есть статья под названием Как обрабатывать недостающую информацию с помощью S-by-c на Третий Манифест веб-сайт, который управляется Хью Дарвен и C. J. дата. Это не написано C. J. Date, но я предполагаю, что, поскольку это одна из статей на этом веб-сайте, она, вероятно, похожа на его мнения.


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

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

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

во-первых, обратите внимание, что дата и собственный по-настоящему реляционный язык Дарвена Учебник D имеет только один тип соединения, являющийся естественным соединением. Этот обоснование заключается в том, что на самом деле требуется только один тип соединения.

дата книга, на которую я ссылался, является отличным SQL и реляционная теория: Как написать точный код SQL:

4.6: замечание о внешнем соединении: "говоря относительно, [внешнее соединение] a вид ружейного брака: он заставляет таблицы в своего рода союз-да, я означает ли объединение, а не присоединение-даже если таблицы, о которых идет речь, не соответствовать обычным требованиям Союза... Он это, в эффект, путем заполнения одной или обеих таблиц нулями перед выполнением союз, тем самым заставляя их соответствовать обычным требованиям в конце концов. Но нет причин, почему бы не сделать подкладку. с правильными значениями вместо nulls

используя Ваш пример и значение по умолчанию "1900-01-01" как "заполнение", альтернатива внешнему соединению может выглядеть так:

SELECT p.PersonID, p.Name, b.DateOfBirth
  FROM Person AS p
       INNER JOIN BirthDate AS b
          ON p.PersonID = b.PersonID
UNION
SELECT p.PersonID, p.Name, '1900-01-01' AS DateOfBirth
  FROM Person AS p
 WHERE NOT EXISTS (
                   SELECT * 
                     FROM BirthDate AS b
                    WHERE p.PersonID = b.PersonID
                  );

в статье Дарвена прозы две явные таблицы, скажем BirthDate и BirthDateKnown, но SQL не будет сильно отличаться, например, semi join to BirthDateKnown вместо полу разницы в BirthDate выше.

Примечание выше использует JOIN и INNER JOIN только потому, что стандартный SQL-92 NATURAL JOIN и UNION CORRESPONDING не широко реализованы в реальных продуктах SQL (не могу найти цитату, но IIRC Darwen в значительной степени отвечал за последние два, сделав его стандартом).

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

Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth;

одной из альтернатив может быть объект-атрибут-значение модель:

 entity  attribute    value
 1       name         Banana Man
 1       birthdate    1968-06-20

Если дата рождения неизвестна, вы просто опустите ее строку.


Вариант 3: Onus на записи:

CREATE TABLE Person
(
  PersonId int PRIMARY KEY IDENTITY(1,1),
  Name nvarchar(100) NOT NULL,
  DateOfBirth datetime NOT NULL
)

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


вы можете устранить null в выходных данных, а также с помощью COALESCE.

SELECT personid  /*primary key, will never be null here*/
       , COALESCE(name, 'no name') as name
       , COALESCE(birthdate,'no date') as birthdate
FROM people

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


один из вариантов - использовать explicit опции типа, аналогично Хаскеллу Maybe функтор.

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

это восстанавливает своего рода "null" только для тех атрибутов, где вы явно запрашиваете его, но без nullглупая трехзначная логика. Nothing == Nothing is True, а не unknown или null.

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

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown

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

Короче, я согласитесь с APCи onedaywhen ответы о 6NF.