Тони Хоар, който най-вече се споменава като изобретател на препратката NULL, сега я нарича грешка за милиард долари, от която почти всички езици сега „страдат“, включително SQL.
Цитирайки Тони (от неговата статия в Уикипедия):
Наричам го моя грешка за милиард долари. Това беше изобретяването на нулевата препратка през 1965 г. По това време проектирах първата изчерпателна система от типове за препратки в обектно-ориентиран език (ALGOL W). Целта ми беше да гарантирам, че всяко използване на препратки трябва да бъде абсолютно безопасно, като проверката се извършва автоматично от компилатора. Но не можах да устоя на изкушението да сложа нулева препратка, просто защото беше толкова лесно да се приложи. Това доведе до безброй грешки, уязвимости и системни сривове, които вероятно са причинили милиарди долари болка и щети през последните четиридесет години.Интересното тук е, че Тони беше изкушен да приложи тази препратка, защото беше лесно да се направи. Но защо изобщо му трябваше такава справка?
Различните значения на NULL
В един перфектен свят няма да имаме нужда от NULL. Всеки човек има име и фамилия. Всеки човек има рождена дата, работа и т.н. Или има?
За съжаление не го правят.
Не всички държави използват концепцията за собствени и фамилни имена.
Не всички хора имат работа. Или понякога не знаем тяхната работа. Или не ни интересува.
Тук NULL е изключително полезен. NULL може да моделира всички тези състояния, които всъщност не искаме да моделираме. NULL може да бъде:
- Стойността „недефинирана“ , т.е. стойността, която все още не е дефинирана (вероятно по технически причини), но може да бъде дефинирана по-късно. Помислете за човек, който искаме да добавим към базата данни, за да го използваме в други таблици. На по-късен етап ще добавим работата на този човек.
- Стойността „неизвестна“ , т.е. стойността, която не знаем (и може никога да не знаем). Може би вече не можем да питаме този човек или неговите близки за рождената им дата – информацията ще бъде загубена завинаги. Но ние все още искаме да моделираме човека, така че използваме NULL в смисъла на НЕИЗВЕСТНО (което е истинското му значение в SQL, както ще видим по-нататък).
- Стойността „по избор“ , т.е. стойността, която не е необходимо да се дефинира. Имайте предвид, че „незадължителната“ стойност се появява и в случай на OUTER JOIN, когато външното присъединяване не произвежда никакви стойности от едната страна на връзката. Или също при използване на ГРУПИРАЩИ НАБОРИ, където се комбинират различни комбинации от колони GROUP BY (или се оставят празни).
- Стойността „изтрито“ или „избегнато“ , т.е. стойността, която не искаме да указваме. Може би обикновено регистрираме семейното положение на дадено лице, както се прави в някои юрисдикции, но не и в други, където не е законно регистрирането на лични данни от този тип. Следователно не искаме да знаем тази стойност в някои случаи.
- „Специалната“ стойност в даден контекст , т.е. стойността, която не можем да моделираме по друг начин в диапазона от възможни стойности. Това често се прави при работа с периоди от време. Да приемем, че работата на дадено лице е ограничена от две дати и ако лицето в момента работи на тази позиция, ще използваме NULL, за да кажем, че периодът е неограничен в края на периода от време.
- „Случайното“ NULL , т.е. стойността NULL, която е просто NULL, защото разработчиците не са обърнали внимание. При липса на изрично ограничение NOT NULL повечето бази данни приемат, че колоните могат да бъдат нулеви. И след като колоните са нулирани, разработчиците може просто „случайно“ да поставят стойности NULL в редовете си, където дори не са възнамерявали.
Както видяхме по-горе, това са само избрани няколко от 50-те нюанса на NULL .
Следният пример показва различни различни значения на NULL в конкретен пример за SQL:
CREATE TABLE company ( id int NOT NULL, name text NOT NULL, CONSTRAINT company_pk PRIMARY KEY (id) ); CREATE TABLE job ( person_id int NOT NULL, start_date date NOT NULL, -- If end_date IS NULL, the “special value” of an unbounded -- interval is encoded end_date date NULL, description text NOT NULL, -- A job doesn’t have to be done at a company. It is “optional”. company_id int NULL, CONSTRAINT job_pk PRIMARY KEY (person_id,start_date), CONSTRAINT job_company FOREIGN KEY (company_id) REFERENCES company (id) ); CREATE TABLE person ( id int NOT NULL, first_name text NOT NULL, -- Some people need to be created in the database before we -- know their last_names. It is “undefined” last_name text NULL, -- We may not know the date_of_birth. It is “unknown” date_of_birth date NULL, -- In some situations, we must not define any marital_status. -- It is “deleted” marital_status int NULL, CONSTRAINT person_pk PRIMARY KEY (id), CONSTRAINT job_person FOREIGN KEY (person_id) REFERENCES person (id) );
Хората винаги са спорили за липсата на стойност
Когато NULL е толкова полезна стойност, защо хората продължават да го критикуват?
Всички тези предишни случаи на използване на NULL (и други) са показани в тази интересна, скорошна беседа на C.J. Date относно „Проблемът с липсващата информация“ (гледайте видеоклипа в YouTube).
Съвременният SQL може да направи много страхотни неща, за които малко разработчици на езици с общо предназначение като Java, C#, PHP не са наясно. Ще ви покажа пример по-долу.
В известен смисъл CJ Date е съгласен с Тони Хоар, че (зло)използването на NULL за всички тези различни видове „липсваща информация“ е много лош избор.
Например в електрониката подобни техники се прилагат за моделиране на неща като 1, 0, „конфликт“, „неприсвоен“, „неизвестен“, „не ме интересува“, „висок импеданс“. Забележете обаче как в електрониката различни специални стойности се използват за тези неща, вместо една специална стойност NULL . Това наистина ли е по-добре? Как се чувстват програмистите на JavaScript относно разликата между различни „фалшиви“ стойности, като „null“, „undefined“, „0“, „NaN“, празния низ „“? Това наистина ли е по-добре?
Говорейки за нула:Когато напуснем SQL пространството за момент и отидем в математиката, ще видим, че древните култури като римляните или гърците са имали същите проблеми с числото нула. Всъщност те дори нямаха начин да представят нула за разлика от други култури, както може да се види в статията на Уикипедия за числото нула. Цитат от статията:
Записите показват, че древните гърци изглеждат несигурни относно статута на нула като число. Те се запитаха:„Как нищо може да бъде нещо?“, което доведе до философски, а от Средновековието и религиозни аргументи за природата и съществуването на нулата и вакуума.Както виждаме, „религиозните аргументи“ очевидно се простират до компютърните науки и софтуера, където все още не знаем със сигурност какво да правим с липсата на стойност.
Обратно към реалността:NULL в SQL
Докато хората (включително академичните) все още не са съгласни по въпроса дали имаме нужда от кодиране за „недефинирано“, „неизвестно“, „по избор“, „изтрито“, „специално“, нека се върнем към реалността и лошите части за SQL е NULL.
Едно нещо, което често се забравя, когато се работи с NULL на SQL, е, че той официално прилага случая UNKNOWN, който е специална стойност, която е част от така наречената тризначна логика, и го прави непоследователно, напр. в случай на операции UNION или INTERSECT.
Ако се върнем към нашия модел:
Ако, например, искаме да намерим всички хора, които не са регистрирани като женени, интуитивно бихме искали да напишем следното изявление:
SELECT * FROM person WHERE marital_status != 'married'
За съжаление, поради логиката с три стойности и NULL на SQL, заявката по-горе няма да върне онези стойности, които нямат изричен marital_status. Следователно ще трябва да напишем допълнителен, изричен предикат:
SELECT * FROM person WHERE marital_status != 'married' OR marital_status IS NULL
Или ние принуждаваме стойността към някаква стойност НЕ NULL, преди да я сравним
SELECT * FROM person WHERE COALESCE(marital_status, 'null') != 'married'
Логиката с три стойности е трудна. И това не е единственият проблем с NULL в SQL. Ето още недостатъци от използването на NULL:
- Има само едно NULL, когато наистина искахме да кодираме няколко различни „отсъстващи“ или „специални“ стойности. Обхватът от полезни специални стойности зависи силно от домейна и типовете данни, които се използват. Въпреки това винаги се изисква познаване на домейна, за да се тълкува правилно значението на колона с нула, а заявките трябва да бъдат проектирани внимателно, за да се предотврати връщането на грешни резултати, както видяхме по-горе.
- Отново, логиката с три стойности е много трудна за оправяне. Въпреки че горният пример все още е доста прост, какво мислите, че ще даде следната заявка?
SELECT * FROM person WHERE marital_status NOT IN ('married', NULL)
Точно така. Това няма да доведе до нищо, както е обяснено в тази статия тук. Накратко, горната заявка е същата като долната:
SELECT * FROM person WHERE marital_status != 'married' AND marital_status != NULL -- This is always NULL / UNKNOWN
-
Базата данни на Oracle третира NULL и празния низ '' като едно и също нещо. Това е много сложно, тъй като няма да забележите веднага защо следната заявка винаги връща празен резултат:
SELECT * FROM person WHERE marital_status NOT IN ('married', '')
-
Oracle (отново) не поставя NULL стойности в индекси. Това е източникът на много неприятни проблеми с производителността, например, когато използвате колона с нулева стойност в предикат NOT IN като такъв:
SELECT * FROM person WHERE marital_status NOT IN ( SELECT some_nullable_column FROM some_table )
С Oracle горното анти-обединяване ще доведе до пълно сканиране на таблицата, независимо дали имате индекс на some_nullable_column. Поради логиката с три стойности и тъй като Oracle не поставя NULL в индекси, машината ще трябва да удари таблицата и да провери всяка стойност, само за да се увери, че няма поне една стойност NULL в набора, което би направило цяло сказуемо НЕИЗВЕСТНО.
Заключение
Все още не сме решили проблема с NULL в повечето езици и платформи. Макар да твърдя, че NULL НЕ е грешката за милиард долари, за която Тони Хоър се опитва да се извини, NULL със сигурност също е далеч от перфектното.
Ако искате да останете в безопасност с дизайна на вашата база данни, избягвайте NULL на всяка цена, освен ако нямате абсолютно никаква нужда от една от тези специални стойности за кодиране с NULL. Не забравяйте, че тези стойности са:„undefined“, „unknown“, „optional“, „deleted“ и „special“ и още:50-те нюанса на NULL . Ако не сте в такава ситуация, винаги по подразбиране добавяйте ограничение NOT NULL към всяка колона във вашата база данни. Вашият дизайн ще бъде много по-чист, а производителността ви много по-добра.
Ако само NOT NULL беше по подразбиране в DDL и NULLABLE ключовата дума, която трябваше да бъде зададена изрично...
Какви са вашите възгледи и опит с NULL? Как би работил по-добрият SQL според вас?
Лукас Едер е основател и главен изпълнителен директор на Data Geekery GmbH, разположена в Цюрих, Швейцария. Data Geekery продава продукти и услуги за бази данни около Java и SQL от 2013 г.
Още от магистърското си обучение в EPFL през 2006 г., той е очарован от взаимодействието на Java и SQL. По-голямата част от този опит той е придобил в швейцарската сфера на електронното банкиране чрез различни варианти (JDBC, Hibernate, предимно с Oracle). Той се радва да споделя тези знания на различни конференции, JUG, вътрешни презентации и своя фирмен блог.