Принципово кажучи, немає нічого поганого з NULL в багатоколонковому первинному ключі. Але мати такі наслідки дизайнер, ймовірно, не мав наміру, саме тому багато систем видають помилку при спробі цього.
Розглянемо випадок версій модулів / пакетів, що зберігаються у вигляді рядів полів:
CREATE TABLE module
(name varchar(20) PRIMARY KEY,
description text DEFAULT '' NOT NULL);
CREATE TABLE version
(module varchar(20) REFERENCES module,
major integer NOT NULL,
minor integer DEFAULT 0 NOT NULL,
patch integer DEFAULT 0 NOT NULL,
release integer DEFAULT 1 NOT NULL,
ext varchar(20),
notes text DEFAULT '' NOT NULL,
PRIMARY KEY (module, major, minor, patch, release, ext));
Перші 5 елементів первинного ключа - це регулярно визначені частини версії випуску, але деякі пакунки мають індивідуальне розширення, яке зазвичай не є цілим числом (наприклад, "rc-foo" або "vanilla" або "beta" або будь-який інший для когось кому чотири поля недостатньо, можливо, присниться). Якщо пакет не має розширення, то він є НУЛЛ у наведеній вище моделі, і шкоди не було б заподіяно, залишивши речі таким чином.
Але що таке НУЛЬ? Він повинен представляти брак інформації, невідомого. Однак, можливо, це має більше сенсу:
CREATE TABLE version
(module varchar(20) REFERENCES module,
major integer NOT NULL,
minor integer DEFAULT 0 NOT NULL,
patch integer DEFAULT 0 NOT NULL,
release integer DEFAULT 1 NOT NULL,
ext varchar(20) DEFAULT '' NOT NULL,
notes text DEFAULT '' NOT NULL,
PRIMARY KEY (module, major, minor, patch, release, ext));
У цій версії "ext" частина кортежу НЕ NULL, а за замовчуванням на порожній рядок - семантично (і практично) відрізняється від NULL. NULL - це невідомо, тоді як порожній рядок - це навмисний запис про те, що "чогось немає". Іншими словами, "порожній" і "нульовий" - це різні речі. Його різниця між "я не маю значення тут" і "я не знаю, яке значення тут".
Коли ви реєструєте пакет, якому не вистачає розширення версії, ви знаєте, що йому не вистачає розширення, тому порожня рядок насправді є правильним значенням. NULL був би правильним, лише якщо ви не знали, має він розширення чи ні, або ви знали, що це так, але не знали, що це таке. Цю ситуацію простіше вирішити в системах, де значення рядків є нормою, оскільки немає способу представити "порожнє ціле число", окрім вставлення 0 або 1, яке буде завершено згортанням у будь-яких порівняннях, зроблених пізніше (що має власні наслідки) *.
До речі, обидва способи є дійсними в Postgres (оскільки ми обговорюємо "корпоративну" RDMBS), але результати порівняння можуть дещо відрізнятися, коли ви кидаєте в суміш NULL - тому що NULL == "не знаю", тому всі результати порівняння за участю NULL, що закінчується NULL, оскільки ви не можете знати щось невідоме. НЕБЕЗПЕЧНО! Подумайте над цим: це означає, що результати порівняння NULL поширюються за допомогою ряду порівнянь. Це може бути джерелом тонких помилок при сортуванні, порівнянні тощо.
Postgres передбачає, що ви дорослий, і можете прийняти це рішення для себе. Oracle та DB2 припускають, що ви не усвідомлювали, що робите щось дурне і припускаєте помилку. Це , як правило , правильно, але не завжди - ви , можливо , на самому ділі не знаю , і мають значення NULL в деяких випадках і , отже , залишаючи рядок з невідомим елементом , проти якого змістовні порівняння неможливі це правильна поведінка.
У будь-якому випадку вам слід прагнути усунути кількість полів NULL, які ви дозволяєте, для всієї схеми і вдвічі, якщо мова йде про поля, які є частиною первинного ключа. У переважній більшості випадків наявність стовпців NULL є вказівкою на ненормалізовану (на відміну від навмисно денормалізованої) конструкції схеми, і перед її прийняттям слід дуже подумати над цим.
[* ПРИМІТКА. Можна створити користувацький тип, який представляє собою об'єднання цілих чисел і тип "нижній", який би семантично означав "порожній" на відміну від "невідомого". На жаль, це вводить трохи складності в операціях порівняння, і зазвичай це справді правильний тип, не варто докладати зусиль на практиці, тому що вам взагалі не слід допускати багато NULL
значень. З цього приводу було б чудово, якби RDBMS включав BOTTOM
тип за замовчуванням на додаток до того, щоб NULL
запобігти звичці випадково поєднувати семантику "без значення" з "невідомим значенням". ]