Чому ми не повинні дозволити NULL?


125

Я пам’ятаю, що читав цю статтю про дизайн баз даних, і я також пам’ятаю, що вона повинна мати властивості поля NOT NULL. Я не пам'ятаю, чому це було так.

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

Але що ви робите у разі дати, дати та часу (SQL Server 2008)? Вам доведеться скористатися якоюсь історичною датою чи датою знизу.

Будь-які ідеї з цього приводу?


4
Ця відповідь має уявлення про використання NULL dba.stackexchange.com/questions/5176/…
Дерек Дауні

10
Дійсно? Чому RDBMS дозволяє взагалі використовувати NULL, якщо ми не повинні їх використовувати? З NULL немає нічого поганого, якщо ви знаєте, як з ними боротися.
Fr0zenFyr

3
Це моделювання даних BI? Ви, як правило, не повинні дозволяти нулям насправді таблиць ... в іншому випадку, нулі є вашими друзями при правильному використанні. =)
sam yi

2
@ Fr0zenFyr, тому що RDBMS дозволяє нам щось робити, це не обов'язково добре робити. Ніщо не змушує нас оголосити первинний ключ або унікальний ключ у таблиці, але, за невеликими винятками, ми це все одно робимо.
Леннарт

3
Я думаю, що повне звернення до цієї теми повинно було б посилатися на первісну вимогу Кодда про те, що RDBMS повинен мати систематичний спосіб обробки відсутніх даних. У реальному світі бувають ситуації, коли створюється місце для даних, але немає даних, які можна вносити до них. Архітектор даних повинен відповісти на це, будь то дизайн дизайну баз даних, програмування додатків або те й інше. SQL NULL менш ніж ідеальний у виконанні цієї вимоги, але краще, ніж взагалі нічого.
Уолтер Мітті

Відповіді:


229

Я думаю, що питання погано сформульоване, оскільки формулювання означає, що ви вже вирішили, що NULL є поганими. Можливо, ви мали на увазі "Чи слід дозволити NULL?"

У будь-якому випадку, ось моя думка про це: я думаю, що NULL - це хороша річ. Коли ви починаєте запобігати NULLs лише тому, що "NULLs are bad" або "NULLs are hard", ви починаєте створювати дані. Наприклад, що робити, якщо ви не знаєте моєї дати народження? Що ти збираєшся поставити в стовпчик, поки не дізнаєшся? Якщо ви щось подібне до багатьох людей, що не мають сили NULL, ви збираєтесь ввести 1900-01-01. Зараз я буду поміщений у геріатричну палату і, ймовірно, мені дзвонять з місцевої станції новин, вітаючи мене з довгим життям, розпитуючи мене про свої секрети проживання такого довгого життя тощо.

Якщо рядок можна вводити там, де можливо, що ви не знаєте значення стовпця, я думаю, що 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 = невідомий і невідомий = хибний. Я не впевнений, які проблеми це може спричинити, крім того, що люди не народжуються, знаючи про це (як і вони не народжуються, знаючи багато речей, притаманних складній RDBMS). Знову махнув руками і сказав: "Проблема! Катастрофа!" не робить це так.
Аарон Бертран

57

Встановлені причини:

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

  • NULL розбиває двозначне (знайоме True або False) логіку і вимагає тризначної логіки. Це набагато складніше навіть правильно реалізовувати, і це, звичайно, погано розуміється більшістю DBA та майже всі не-DBA. Як наслідок, він позитивно запрошує в програму багато тонких помилок .

  • Смислове значення якої - небудь конкретної NULL залишається додатком , в відміну від фактичних значень.

    Семантика на кшталт “не застосовується” та “невідомо” та “дозорний” є загальними, є й інші. Вони часто використовуються одночасно в одній базі даних, навіть в одному і тому ж відношенні; і, звичайно, є неявними і нерозрізними і несумісними значеннями.

  • Вони не потрібні реляційним базам даних , як це зазначалося у розділі "Як поводитися з відсутньою інформацією без нуля" . Подальша нормалізація - очевидний перший крок, щоб спробувати позбавити таблицю NULL.

Це не означає, що NULL ніколи не повинен бути дозволений. Це дійсно стверджує , що є багато причин , щоб заборонити NULL там , де це можливо.

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

Фабіан Паскаль відповідає на ряд аргументів у "Нульових скасованих" .


3
Посилання на "Як поводитись з відсутністю інформацією без нуля" доволі чудово показує, чому ми не можемо обійтися без нулей: Деякі пропозиції було б неможливо раціонально реалізувати на основних RDBMS, як вони є.
Джек Дуглас

7
Джек: Правильно, але "поточні реалізації не можуть цього зробити" - це не аргумент для статусного кво :-)
bignose

17
Це таке, як сказати, що ми не повинні літати, бо літаки не є ідеальними?
Аарон Бертран

11
Ні, це говорить про те, що постачальники повинні припинити звертатися з виправданнями для нулів, які, можливо, були дійсні сорок років тому, але давно пережили свій розумний термін зберігання. Часи вводу / виводу вже не в порядку 80 мс. Одиночні цикли процесора вже не в порядку величини мікросекунд. Обмеження пам’яті вже не в порядку кількох мег. На відміну від сорока років тому, апаратні швидкості та потужності, необхідні для роботи без нулей, тепер існують, при цьому вартість не є надмірною. Він каже, що пора рухатися далі.
Ервін Смоут

2
Посилання "NULL плутанини" мертве.
jpmc26

32

Я не згоден, нулі є важливим елементом проектування бази даних. Альтернативою, як ви також нагадали, було б розповсюдження відомих значень для відображення відсутніх або невідомих. Проблема полягає в тому, що нуль настільки неправильно розуміється, і в результаті використовується невідповідно.

IIRC, Кодд висловив припущення, що поточна реалізація нуля (мається на увазі немає / відсутня) може бути покращена за рахунок наявності двох нульових маркерів, а не одного, "не присутнього, але застосовного" та "немає та не застосовується". Не можу уявити, як реляційні конструкції будуть покращені цим особисто.


2
Я пропоную встановити набір користувачем різного виду nullта визначену користувачем багатозначну логіку, щоб разом із ними: p
Джек Дуглас

13
Це не єдині варіанти. Ви виключаєте альтернативу нормалізації. Замість стовпців, які можуть мати або не мати значення, використовуйте іншу таблицю, яка може мати або не мати відповідного рядка для першої таблиці. Сенс присутності або відсутності рядка вводиться в значення таблиць, і немає спеціального обліку значень NULL або дозорних значень тощо
bignose

7
Наявність NULL не вимагає значень спеціального кожуха або дозорних значень. Це лише симптоми того, як деякі люди вирішують мати справу з NULL.
Аарон Бертран

Варто зауважити, що "" відрізняється від "null" на PostgreSQL (хоча це не Oracle), і це дає двократний маркер, і ви можете використовувати 0 для числових стовпців. Проблема з 0 хоча полягає в тому, що він не працює для сторонніх ключів.
Кріс Траверс

13

Дозвольте почати з того, що я не DBA, я напам'ять розробник, і я підтримую та оновлюю наші бази даних, виходячи з наших потреб. Коли це було сказано, у мене було те саме питання з кількох причин.

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

Я проводжу дуже багато часу, переглядаючи безліч відповідей, коментарів, статей та порад по всьому Інтернету. Потрібно сказати, що більшість інформації були приблизно такими ж, як відповідь @ AaronBertrand. Саме тому я відчув потребу відповісти на це питання.

По-перше, я хочу встановити щось прямо для всіх майбутніх читачів ... Значення NULL представляють невідомі дані, НЕ невикористані дані. Якщо у вас є таблиця працівників, у якій є дата дати припинення. Нульове значення в даті закінчення полягає в тому, що це майбутнє обов'язкове поле, яке наразі невідоме. Кожен працівник, будь він активним чи припиненим, в якийсь момент додасть дату до цього поля. Це, на мою думку, єдина і єдина причина для Полезного поля.

Якщо говорити, той самий стіл працівника, швидше за все, містить деякі дані автентифікації. У корпоративному середовищі звичайно, що працівники будуть перераховані в базі даних для персоналу та бухгалтерського обліку, але не завжди мають або потребують деталей аутентифікації. Більшість відповідей призведе до того, що ви вважаєте, що нормально скасувати ці поля або в деяких випадках створити для них обліковий запис, але ніколи не надсилайте їм облікові дані. Перший змусить вашу команду розробників написати код, щоб перевірити наявність NULL і відповідати їм відповідним чином, а другий створює величезний ризик для безпеки! Облікові записи, які ще ніколи не використовуються в системі, лише збільшують кількість можливих точок доступу для хакера, плюс вони займають цінний простір бази даних для того, що ніколи не використовується.

З огляду на інформацію, подану вище, найкращий спосіб поводитись із зведеними даними, які використовуються, - це дозволити нульові значення. Це сумно, але правда, і ваші розробники зненавидять вас за це. Другий тип зведених даних повинен бути поміщений у відповідну таблицю (IE: рахунок, облікові дані та ін.) І мати відношення один на один. Це дозволяє користувачеві існувати без облікових даних, якщо вони не потрібні. Це знімає додатковий ризик безпеки, цінний простір бази даних та забезпечує набагато більш чисту базу даних.

Нижче представлена ​​дуже спрощена структура таблиці, що показує як необхідний нульовий стовпець, так і відношення один на один.

Невідомі стосунки Nullable і один на один

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


2
Я просто змінив би це так, щоб його не було TerminationDateв записах працівника, але мати таблицю, за TerminatedEmployeeякою працівники під час їх припинення переміщуються (не копіюються) заявою. Очевидно, це добре працює з таблицею облікових записів, оскільки на ньому не буде пов'язаного облікового запису TerminatedEmployee. Якщо вам все-таки потрібні номери телефонів, я перевернув би сторонні ключі, щоб у службових та скасованих службових таблиць з'явився ідентифікатор номера телефону, а не навпаки.
Programster

2
Я міг буквально продовжувати цілими днями про те, чому це було б погано. Надлишки таблиць, погані практики SQL, завдяки чому ваші розробники повинні шукати в двох місцях дані про співробітників, проблеми зі звітуванням, проблеми з прямими URI для працівника, якого немає (переміщено), і список продовжується і на. Зовсім прекрасно мати NULLS для полів, які колись матимуть значення, це вже інша історія - поля, які ніколи не заповнюються і ніколи не користуються. Ряд потенційних проблем і шляхів вирішення цієї роботи не вартує невеликого питання перевірки наявності NULL на полі.
Ніколас Агірре

1
Я не погоджуюсь. Єдине, що зайве - це нульове поле для дати закінчення, яке ніколи не може бути заповнене. Розробникам залишається лише шукати у відповідній таблиці ті дані, які вони хочуть, і це може підвищити ефективність. Якщо з якоїсь причини ви хочете, як співробітників, які припиняються, так і не припиняють співробітників, це вирішується приєднанням, але 90% часу ваша заявка, ймовірно, захоче того чи іншого. Я думаю, що вказаний мною макет кращий, оскільки неможливо мати дату припинення роботи працівника, а для нього все-таки мати обліковий запис.
Programster

2
Я не казав зайвих даних, я казав зайві таблиці. Плюс до того, будь-які зміни в таблицях співробітників повинні зводитись до скасованих таблиць; це робить програму сприйнятливою до помилок і значно ускладнює роботу розробника. Крім того, поле для дати припинення буде заповнене майже для всіх. Марно і проблематично створювати другу ідентичну структуру таблиці, а також переміщувати дані. Не включати тестування кожен раз, щоб переконатися, що дані таблиці переміщені та очищені. Видаляти дані з таблиці погано, навіть якщо тільки перемістити їх. Якщо ви настільки переймаєтесь одним полем, що ...
Ніколас Агірре

1
... які майже завжди будуть заповнені в часі, тоді складіть припинену таблицю з відносинами 1to1 назад до працівника. Я цілий день працюю з різноманітними базами даних як DBA, і як розробник, і я радий, що ще не стикався з запропонованою вами структурою. Особливо з точки зору розробника, це було б кошмаром, щоб писати та помилятись, щоб перевірити все, тому що ви не знаєте, з якого столу він береться. Навіть записуючи приєднання, дані, повернені до програмного забезпечення, матимуть поле з нульовими даними, яке все ще вимагатиме від вас перевірки.
Nicholas Aguirre

13

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

Стовпчики NULL'able - це катастрофа з точки зору продуктивності. Розглянемо арифметику цілих чисел як приклад. У розумному світі без NULL легко "векторизувати" цілочисельну арифметику в коді двигуна бази даних, використовуючи інструкції SIMD, щоб виконати майже будь-який обчислення зі швидкістю швидше, ніж 1 рядок за цикл процесора. Однак, коли ви вводите NULL, вам потрібно обробити всі особливі випадки, які створює NULL. Сучасні набори інструкцій для процесора (читайте: x86 / x64 / ARM та логіка графічного процесора) просто не обладнані для цього.

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

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 та можливе неіснуюче значення даних (наприклад, порожній рядок для рядків).

Це складніше, ніж це. Null має цілий ряд чітких значень, і одна з дійсно важливих причин не допускати нулів у багатьох стовпцях полягає в тому, що коли стовпець є нульовим, це означає одне і лише одне (а саме те, що воно не відображалось у зовнішньому з'єднанні). Крім того, це дозволяє встановити мінімальні стандарти введення даних, що дуже корисно.

Але що ви робите у разі дати, дати та часу (SQL Server 2008)? Вам доведеться скористатися якоюсь історичною датою чи датою знизу.

Це ілюструє проблему з нулями відразу, а саме те, що значення, збережене в таблиці, може означати або "це значення не застосовується", або "ми не знаємо". Для рядків порожній рядок може слугувати як "це не застосовується", але з датами та часом немає такої умовності, оскільки немає дійсного значення, яке умовно означає це. Зазвичай там ви застряжете за допомогою NULL.

Існують способи подолати це (додавши більше відносин та з'єднавшись), але вони створюють ті ж самі проблеми з семантичною ясністю, що й NULL в базі даних. Для цих баз даних я б не хвилювався з цього приводу. Просто не можна нічого з цим зробити насправді.

EDIT: Одна з областей , де значення NULL є незамінними в зовнішніх ключах. Тут вони, як правило, мають лише одне значення, ідентичне нулю в зовнішньому значенні приєднання. Це виняток із проблеми, звичайно.


10

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

Просто пам’ятайте, як ваша RDBMS обробляє їх у SELECT операціях, таких як математика, а також в індексах.


-12

Нічого собі, правильна відповідь "Не дозволяйте NULL, коли вам не доведеться, бо вони погіршують продуктивність" - якимось чином останньою оцінкою. Я буду його схвалювати і деталізувати. Коли RDBMS дозволяє NULL для нерозрідженого стовпця, цей стовпець додається до растрової карти, яка відстежує, чи є значення NULL для кожного окремого рядка. Отже, додаючи NULL-здатність до стовпця в таблиці, де всі стовпці не дозволяють NULL, ви збільшуєте місце для зберігання, необхідне для збереження таблиці. Крім того, ви вимагаєте від RDBMS для читання та запису до растрової карти, що погіршує продуктивність усіх операцій.

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

У таблиці Person є стовпець під назвою DateOfDeath, який є нульовим. Якщо людина померла, вона заповнюється її DateOfDeath, інакше вона залишиться NULL. Також є ненульовий бітовий стовпець під назвою IsAlive. У цьому стовпці встановлено 1, якщо людина жива, і 0, якщо людина померла. Переважна більшість збережених процедур використовує стовпчик IsAlive, вони дбають лише про те, чи жива людина, а не їх DateOfDeath.

Однак стовпчик IsAlive порушує нормалізацію бази даних, оскільки вона повністю виведена з DateOfDeath. Але оскільки IsAlive є провідним для більшості SP, то прямим рішенням є зробити DateOfDeath ненульовим і призначити значення стовпця за замовчуванням у випадку, якщо людина ще жива. Після цього кілька SP, які використовують DateOfDeath, можуть бути переписані для перевірки стовпця IsAlive і лише вшановувати DateOfDeath, якщо людина не живе. Знову ж таки, оскільки більшість SP не піклуються лише про 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 в таблиці, ви зможете повернути значну кількість місця для зберігання.

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

Також зауважте, що це стосується лише справжніх RDBMS, і я базую технічну частину своїх відповідей на SQL Server. Перелічений T-SQL для пошуку зведених стовпців без нулів також є від SQL Server.


1
Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Пол Білий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.