Pull to refresh

Comments 152

Есть ещё один очень популярный язык программирование в котором всё время приходится учитывать троичную логику (и даже в каком-то смысле четверичную) — это JavaScript со своими undefined и null
Скрытый текст
var a = {};

console.log(a.b); // undefined
console.log(Object.keys(a)); // []

a.b = undefined;
console.log(a.b); // undefined
console.log(Object.keys(a)); // ["b"]


И не дай боже null через typeof проверять...

В вашем примере всё становится логично, если считать что объекты в JS - это всего лишь Map<String, Object>. Разница между пустым Map и Map, в котором есть запись "b" -> undefined вполне очевидна. Это впрочем никак не оправдывает кучу другой дичи в JS.

Ну тогда уже стоит вспомнить и TypeScript, где проблема nullability решается на уровне типов. Или Kotlin.

Из опыта собеседований на junior-middle позиции, не менее четверти кандидатов не знают как работает сравнение с null.

В отличии от Oracle, в PostgreSQL тип boolean - это first class citizen, а значит троичную логику можно выпилить только с выпиливанием NULL-ов вообще. Если задуматься о смысле значения NULL, то правила работы с логическим NULL-ом устроены достаточно логично.

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

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

Монга не соответствует стандарту SQL, Эластик не соответствует, много кто ещё не соответствует. Но это не мешает людям ими с удовольствием пользоваться.

Вообще, тенденция сейчас такова, что SQL потихоньку просачивается в самые неожиданные места. Не удивлюсь, если скоро приделают запросы к DOM в браузере. Если ещё не приделали. Тащить везде за собой эту глупую концептуальную ошибку с троичной логикой... ну, не обязательно, разве нет?

Давайте я вам помогу: при анализе дампов памяти java в Memory Analyzer'е можно использовать sql-подобный язык для поиска объектов.

Это всё не важно: есть стандарт, которому должны следовать (в меру возможностей) все уважающие себя RDBMS и он общепризнан. Ни монга, ни эластик RDBMS не являются. Давать такие примеры - это всё равно, что говорить, что несущие крылья для самолёта - это концептуальная ошибка, т.к. они занимают много места и вообще, у вертолёта Ми-8 нет таких крыльев и это не мешает ему летать.

Реляционная алгебра тоже вполне применима к данным, лежащим в Монге или Эластике. Помню, как нарисовал ER-диаграмму монговской базы, и это оказалось вот прям очень полезно.

Да, они не SQL. Они "Not only SQL". Но они тоже RDBMS. Не надо ставить знак равенства между SQL и RDBMS.

Вы довольно таки широко трактуете термины, за пределами классического их использования. В такой трактовке, термины теряют свой смысл. Также при этом, PostgreSQL, внезапно, становится настоящей Not only SQL базой, при чём, возможно, в большей степени, чем вышеназванные, ведь она поддерживает настоящий полновесный диалект SQL, выполняя подавляющее большинство стандарта этого языка, а также позволяет использовать её как документоориентированную базу данных за счёт поддержки json/jsonb.

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

Если NULL будет равен сам себе, то внешние соединения по нулевым полям дадут совершенно неинтуитивный результат. Отсутствующее отчество у Джона Смита — это не то же, что отсутствующее отчество у Джимми Вонга

Это как в разборе математических софизмов: ищи, где втихую обе части равенства на ноль домножили или разделили :)

Вот тут как раз интуиция всегда оказывается на стороне того, что NULL=NULL. И чтобы вспомнить, что это "на самом деле" не так, приходится делать над собой усилие. Кстати, в некоторых случаях SQL работает без глупостей. Например:

select mname, count(*)
from persons
group by mname

Кто-нибудь страдает от такого хамского попрания догмы со стороны group by? Неа, все только наслаждаются.

В одном из ответов на stackoverflow приведены интересные цитаты из спецификации SQL. Если кратко: NULL друг другу не равны (not equal), но при этом они неразличимы (not distinct). Таким образом, GROUP BY не попирает догму, а просто использует иную концепцию.

Собственно, SQL2003 определяет оператор IS DISTINCT FROM, а в MySQL есть <=>

NULL друг другу не равны (not equal), но при этом они неразличимы (not distinct)

Я же говорю, единственное место в наследии нашей цивилизации, где идентичность слабее равенства. Это как если бы в JS "===" давало true, а результат "==" интерпретировался бы как false.

Почему Вы упорно пытаетесь свести NOT DISTINCT к равенству/идентичности? Это просто отдельный концепт.

Если уж говорить об аналогиях NULL среди типов данных языков программирования, то для меня это всегда казалось чем-то близким монаде MayBe или типу Option .

А поскольку NULL может случиться где угодно, в Option получаются неявно завёрнуты вообще все значения.

И distinct/равенство, отлично вписываются в эту концепцию: distinct сравнивает сами Option'ы (и возвращает bool), а равенство — их содержимое (и возвращает тоже Option). А вот в JS ничего подобного нет.

а равенство — их содержимое (и возвращает тоже Option)

Зачем? Какой в этом практический смысл? Можете привести пример кода, где это полезно?

Вам ни разу не приходилось делать JOIN по nullable-колонкам? Если ко всем NULLам из одной таблицы приджойнятся все NULLы из другой, будет хорошо и правильно?

Вот тут как раз интуиция всегда оказывается на стороне того, что NULL=NULL

Почему? Просто NULL следует трактовать как неизвестное значение. Равно ли одно неизвестное значение другому неизвестному значению? Неизвестно. Ну т.е. NULL.

Конечно, равны. Undefined === undefined.

Просто NULL следует трактовать как неизвестное значение.

Почему? Почему не как отсутствующее значение? Равно ли отсутствие значения отсутствию значения? Да.

Можно и как отсутствующее. Только нужно понимать, что это не значение поля равняется отсутствию, это значение поля отсутствует в БД, а следовательно для БД оно неизвестно. Ну а дальше все то же, что я написал ранее.

отсутствует в БД, а следовательно для БД оно неизвестно

Нет, отсутствие это именно отсутствие, а не присутствие неизвестного значения. У англичанина отчество именно отсутствует, а не имеется какое-то, но неизвестное БД. NULL показывает отсутствие значения, отсутствие значения равно отсутствию значения.


это не значение поля равняется отсутствию

Тут 2 значения NULL, а не одно. "Значение поля" в конкретной строке это NULL, это показывает отсутствие значения. Оно сравнивается например с константой из WHERE, которая тоже NULL и показывает, что нам нужны записи с отсутствием значения. То есть это не "значение поля равняется отсутствию", а "отсутствие равняется отсутствию".

У англичанина отчество именно отсутствует

SQL не вкладывают какую-то дополнительную семантику в NULL. Сказано - значение отсутствует в БД, и все. Причин по которым оно отсутствует могут быть десятки, это уже не проблема СУБД.

"отсутствие равняется отсутствию"

Стремлением так это трактовать вы сами себе создаете проблемы на ровном месте, при том что исправить их кроме как созданием машины времени и нападения на авторов спецификации SQL невозможно.

Сказано — значение отсутствует в БД, и все.

Ну так я про это и говорю, значение именно отсутствует в БД, а не присутствует, но неизвестно какое. То есть аргументы, основанные на семантике неизвестности, некорректны.


Стремлением так это трактовать вы сами себе создаете проблемы на ровном месте

В языках программирования null == null работает без всяких проблем.

а не присутствует, но неизвестно какое.

Это то же самое. Если значение отсутствует в БД, то оно по определению неизвестно для БД. NULL это не часть диапазона возможных значений поля, это специальное слово, указывающее что значения нет. А потому и сравнивать по значению ничего нельзя - его нет.

В языках программирования null == null работает без всяких проблем.

В языках программирования у null совсем другая семантика, которая исторически уходит корнями в нулевой указатель на тип. В то же время в языках программирования NaN == NaN дает false, потому что слева нет определенного значения, и справа нет определенного значения, а результат сравнения двух неопределенных значений неизвестен.

Это то же самое. Если значение отсутствует в БД, то оно по определению неизвестно для БД.

Вот я как раз и пытаюсь объяснить, что это не то же самое. "Значение неизвестно" подразумевает, что значение есть, но какое точно, неизвестно. А его с большой вероятностью вообще нет. Это является в общем-то наиболее часто моделируемым случаем, потому что моделирование наличия какого-то неизвестного значения для практических целей бесполезно, потому что мы его никак не можем обработать. Там где это все-таки нужно, наличие какого-то неизвестного значения моделируется полем boolean, а не null.


В языках программирования у null совсем другая семантика

Семантика там абсолютно такая же, отсутствие значения, и при загрузке данных из БД NULL напрямую мапится в null языка программирования.


исторически уходит корнями в нулевой указатель на тип

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

"Значение неизвестно" подразумевает, что значение есть, но какое точно, неизвестно. А его с большой вероятностью вообще нет

Не подразумевает. Есть ли оно в принципе, применимо ли оно в принципе, ввел ли его оператор - это проблемы кого угодно, но не СУБД. Это полностью зона ответственности того, кто проектировал БД, и того, кто с ней работает.

Семантика там абсолютно такая же, отсутствие значения

У null в языках программирования есть две наиболее распространенные семантики - отдельный тип с одним единственным значением, и нулевой указатель. И в обоих null является значением. В SQL NULL это указание что такого значения в БД нет. Если бы их сематика совпадала, то в SQL было бы NULL == NULL.

Это как раз нулевой указатель появился исключительно для того, чтобы обозначать отсутствие значения

Ноль это не отсутствие значение указателя, это лишь позиция в которую он указывает. Два указателя указывающие в ноль равны.

Не подразумевает.

Это подразумевают правила русского языка. Мы говорим о значении и даем ему какую-то характеристику (оно неизвестно).


это проблемы кого угодно, но не СУБД

Вот я как раз и говорю, что в рамках СУБД это означает "значение не задано". Просто не задано в СУБД. Отсутствует.


— Семантика там абсолютно такая же, отсутствие значения
— В SQL NULL это указание что такого значения в БД нет.

Не вижу разницы.


И в обоих null является значением.

Он и в SQL является значением, вы можете это значение записать в ячейку. Просто правила работы с ним сделаны по-особенному, о чем и разговор, что это нигде не нужно.


Если бы их сематика совпадала, то в SQL было бы NULL == NULL.

Это зависит не от семантики, а от заданных технических правил работы со значением. Я вот решу в моем приложении, что у меня строка "абырвалг" означает отсутствие значения, и семантика у него будет такая, а две строки с этим значением будут равны и в SQL и в приложении.


Ноль это не отсутствие значение указателя

Ноль в значении указателя по историческим причинам обозначает отсутствие валидного значения заданного типа по этому указателю.

Это подразумевают правила русского языка

Стандарт SQL должен опираться на правила русского языка?

Вот я как раз и говорю, что в рамках СУБД это означает "значение не задано". Просто не задано в СУБД. Отсутствует.

Ну да. А поэтому его нельзя ни с чем сравнить, ведь оно отсутствует в БД.

Не вижу разницы.

В первом случае сравнивается значение, во втором сразу утверждается что значение нет, а потому делать сравнение не с чем.

вы можете это значение записать в ячейку

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

Это зависит не от семантики, а от заданных технических правил работы

Семантика это и есть правила работы. Если правила совпадают - то совпадает и семантика, хотя может отличаться синтаксис. Если правила разные - то и семантика разная, даже если синтаксис одинаковый.

Ноль в значении указателя по историческим причинам обозначает отсутствие валидного значения заданного типа по этому указателю.

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

Стандарт SQL должен опираться на правила русского языка?

— SQL не вкладывает какую-то дополнительную семантику в NULL. Сказано — значение отсутствует в БД, и все.
— Значение именно отсутствует в БД, а не присутствует, но неизвестно какое.
— Это то же самое. Если значение отсутствует в БД, то оно по определению неизвестно для БД.
— "Значение неизвестно" подразумевает, что значение есть, но какое точно, неизвестно.
— Не подразумевает.
— Это подразумевают правила русского языка.


А при чем тут стандарт SQL? Фразу "Значение неизвестно" сказали вы, я говорил про нее. Но в английском, представьте себе, это выражение тоже подразумевает существование значения. И именно на это и опирается стандарт SQL с логикой "одно неизвестное значение не равно другому неизвестному значению". То есть в данном случае он как раз соответствует правилам русского языка. А разговор идет о том, что база не должна делать предположения, что оно есть, но неизвестное, а должна опираться только на то, что оно не задано, и равенство по этому признаку должно давать true, а не "неизвестно".


А поэтому его нельзя ни с чем сравнить, ведь оно отсутствует в БД.

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


Почитайте про историю NULL в SQL.

Какая разница, что там было в истории, у нас разговор о том, что выглядит более логично.


Семантика это и есть правила работы.

Конечно же нет, семантика это смысл, смысл известен только человеку, а не БД. В разных полях одно и то же значение 1 может иметь разную семантику, хотя правила работы с целым числом в обоих полях одинаковые.


Ноль сам по себе ничего не обозначает

Откуда вдруг взялась характеристика "сам по себе"? Зачем вы приписываете мне какие-то вещи, которые я не говорил, и с ними спорите?
Ноль означает отсутствие значения не сам по себе, а только когда он является значением указателя. Люди так договорились и заложили эту модель в компилятор. Ноль в значении типа int отсутствие значения не означает.

база не должна делать предположения, что оно есть, но неизвестное, а должна опираться только на то, что оно не задано, и равенство по этому признаку должно давать true

СУБД и не делает никаких предположений. NULL означает что значения нет в БД. ИРЛ значение может быть, может не быть, может не иметь смысла, может появиться позже и т.д. БД этого не знает и не может знать. Т.е. неизвестным является не только значение, но и факт его существования. Таким образом, NULL ведет себя правильно.

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

Это не более чем буквоедство. Сказать что значение существует, потому что мы сказали слово "значение", это все равно что сказать что у еще не решенного уравнения точно есть корень, потому что в его записи есть "x", якобы просто пока оно неизвестно.

Так мы сравниваем не неизвестное значение

Именно его и сравниванием. И именно поэтому получаем тот результат, который получаем.

Какая разница, что там было в истории

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

Да, оно работает не так как null в императивных языках, хотя синтаксис похож. Но это все равно что математик придет в программирование, и будет возмущаться что "x = 1; y = 2; x = y;" это полный бред, и должно быть вообще не так.

В разных полях одно и то же значение 1 может иметь разную семантику

Не может. Для СУБД у них будет одинаковая семантика. Какой смысл несет поле в реальном мире не имеет значения, это не область ответственности СУБД.

Ноль означает отсутствие значения не сам по себе, а только когда он является значением указателя

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

Вы же наоборот требуете предполагать что значение точно есть.

Нет, я предлагаю считать, что в строке есть признак "значение отсутствует". Как отдельное поле this_column_has_value boolean. NULL именно так и хранится. Два значения false в this_column_has_value ведь равны между собой?


А поэтому его нельзя ни с чем сравнить, ведь оно отсутствует в БД.
Так мы сравниваем не неизвестное значение
Именно его и сравниванием.

Вы уж определитесь, мы его сраниваем, или его нельзя сравнить. Если сравниваем, значит предполагаем наличие, если сравнить нельзя, значит предполагаем отсуствие. Сравнение двух неизвестных значений дает неизвестность, сравнение признаков "значение отсутствует" дает конкретный результат. ((has_value1 = false) === (has_value2 = false)) === true.


Это осознанный, обоснованный теорией баз данных выбор

Это не так. Кодд говорил, что это было неправильное решение.
Null (SQL)
Codd indicated in his 1990 book The Relational Model for Database Management, Version 2 that the single Null mandated by the SQL standard was inadequate, and should be replaced by two separate Null-type markers to indicate the reason why data is missing.


Тут мы возвращаемся к вопросу, заданному автором статьи. В каких конкретно случаях он обоснован? Ни в одном из примеров, приведенных в комментариях, нет никаких проблем если NULL будет равен NULL. Результаты некоторых запросов будут отличаться, но само по себе это не является проблемой, там где NULL не нужен всегда можно добавить условие в WHERE, это сделает запрос только более наглядным.


Не может. Для СУБД у них будет одинаковая семантика.

Ну так и два значения NULL для СУБД имеют одинаковую семантику — значение отсутствует в БД. И такая же семантика у значения NULL в приложении.


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

Ну вот опять. Я ни слова ни говорил о том, что для самого указателя 0 является необычным значением, зачем вы с этим спорите?
0 в указателе обозначает отсутствие значения для вызывающего кода, или для инструмента анализа кода (компилятора).


Описанное в принципе не должно и не может иметь такого поведения как в SQL.

Ну как это не может, когда фактически имеет. В БД NULL означает отсутствие значения и в приложении NULL означает отсутствие значения. А вот правила работы с NULL работают так, как будто это наличие неизвестного значения, о чем и речь.

Нет, я предлагаю считать, что в строке есть признак "значение отсутствует".

Ну допустим. Однако, признак это не само значение. Следовательно, для сравнения значений полей и сравнений признаков полей нужны разные операторы. Собственно, именно так оно сейчас и есть.

Вы уж определитесь, мы его сраниваем, или его нельзя сравнить

Перестаньте заниматься буквоедством. Вот у нас есть "x", есть "y", значения обоих неизвестны. Чему равно "x == y"? Да, мы используем оператор сравнения, и да, мы не можем получить никакой результат кроме "неизвестно".

Это не так. Кодд говорил, что это было неправильное решение.

Это так. И Кодд не говорил что NULL который сейчас нужно убрать или сделать его поведение альтернативным. Он сказал что "NULL - неизвестно, хз почему" нужно разделить на "NULL - неизвестно" и "NULL - неизвестно и неприменимо", которые он назвал A-marks и I-marks. При этом цитирую первое их свойство из книги: "The DBMS does not treat marks as if they were values" и "If I-marks are placed in the top class, A-marks in the second class,and all db-values in the third class, the combination (arithmetic or otherwise) of any two items is an item of whichever class is thehigher of the two operands.". Т.е. теперь не только (UNKNOWN == UNKNOWN) дает UNKNOWN, но и (INAPPLICABLE == INAPPLICABLE) дает INAPPLICABLE, а (UNKNOWN == INAPPLICABLE) дает INAPPLICABLE.

Результаты некоторых запросов будут отличаться, но само по себе это не является проблемой, там где NULL не нужен всегда можно добавить условие в WHERE

Ну раз все так легко и просто, то и обратное будет верно. Только не нужно добавлять дополнительных условий, а единственная поправка это "IS" или "IS NOT DISTINCT FROM" вместо "==", что еще более просто. Причем еще не известно каких поправок требуется в большем количестве.

Ну так и два значения NULL для СУБД имеют одинаковую семантику — значение отсутствует в БД

Ну да. Цитируя все ту же книгу Кодда: "The semantics of the fact that a db-value is missing are not the same as the semantics of the db-value itself.". У NULL как раз та семантика, которая дает NULL для всех операций с ним.

Я ни слова ни говорил о том, что для самого указателя 0 является необычным значением

Вы утверждали что NULL в указателях это то же самое что NULL в SQL, "напрямую мапится", и т.д. А оно не мапится, по причине указанной выше.

0 в указателе обозначает отсутствие значения

Когда мы сравниваем указатели, мы не сравниваем значение в памяти на которую указывает указатель. Мы сравниваем позицию в памяти, а она у указателя есть всегда. Более того, нулевая позиция и отсутствие валидной памяти не являются эквивалентными. А если считать отсутствие валидной памяти по указателю основанием возврата 0, то например сравнение любых двух указателей с одинаковым ненулевым значением тоже должно давать 0 в ряде ситуаций.

Ну как это не может

Очень просто. Если NULL в C будет всегда давать NULL при сравнении, то проверка указателя на нулевое смещение станет невозможной, а это единственное зачем NULL вообще нужен.

в приложении NULL означает отсутствие значения

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

Однако, признак это не само значение.

Конечно, я про это и говорю. INT NULL и INT NOT NULL отличаются наличием дополнительного признака, который хранится вместе со значением.


Собственно, именно так оно сейчас и есть.

Нет, сейчас есть не так. Сравнение двух признаков со значениями true или false дает определенный результат, а не неизвестность, как сейчас.


Вот у нас есть "x", есть "y", значения обоих неизвестны.

"Не заданы", а не "неизвестны". Я говорю о том, что это разные понятия, и это не буквоедство, а прямо влияет на правила работы со значениями. Признаки "Не задано" мы можем сравнить без всякой неизвестности. Ведь мы совершенно точно знаем, что значений в нашей системе нет.


мы не можем получить никакой результат кроме "неизвестно".

Если считать, что они не заданы, то вполне можем. То не задано и это не задано, нам точно известно, что значения этой характеристики одинаковые, значит они равны.


И Кодд не говорил что NULL который сейчас нужно убрать или сделать его поведение альтернативным.

Я говорил про вашу фразу, что это осознанный и обоснованный теорией выбор. Если бы это было так, то Кодд не захотел бы ничего менять, а значит это не так. Чего конкретно хотел Кодд, в данном случае не имеет значения.


Только не нужно добавлять дополнительных условий

Я говорю о том, что дополнительные условия это преимущество, так как дают наглядность. Непонятно, почему вы приводите в качестве аргумента вариант, который с моей точки зрения имеет меньше преимуществ.


а единственная поправка это IS или IS NOT DISTINCT FROM вместо ==

В языке, ориентированном на человека и по форме приближенного к английскому, вместо человекопонятного "равно" надо писать "не отличается от", которое содержит инвертирование того, что мы на самом деле хотим получить? Не вижу ни одного преимущества у такого варианта.


Цитируя все ту же книгу Кодда

С моей точки зрения эта цитата подтверждает мои слова, а не ваши. Семантика NULL отличается от семантики COLOUR, поэтому неправильно говорить, что сравнивая NULL мы сравниваем неизвестные цвета дверей. Мы сравниваем признаки "отсуствует ли цвет". А вот когда они присутствуют, мы уже можем сравнить известные цвета.


А оно не мапится, по причине указанной выше.

Не знаю, может в вашем языке и не мапится, в моем нормально мапится и широко используется как признак отсутствия значения.


Когда мы сравниваем указатели, мы не сравниваем значение в памяти на которую указывает указатель.

А должны? Я вам всю ветку это и объясняю, что неправильно называть сравнение NULL в поле "цвет" сравнением значений цветов, потому что NULL это не значение цвета.


Вывод о невалидности памяти это уже следствие из результата сравнения с ним.

Именно это и означают мои слова "обозначает отсутствие значения".

Нет, сейчас есть не так.

В смысле? Пишем "==" - сравниваем значения, пишем "IS NOT DISTINCT FROM" - сравниваем признаки, и если есть оба значения, то сравниваем уже их.

Ведь мы совершенно точно знаем, что значений в нашей системе нет.

А оператор "==" совершенно точно предназначен только для значений. Которых в нашей системе точно нет.

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

Это и есть осознанный и обоснованный теорией выбор. Во-первых помимо теории практическая реализация должна учитывать ограничения реального мира. Во-вторых это теория изменилась со временем, а не разработчики ее проигнорировали. Ну и в-третьих из необходимости изменений никак не следует что теория не использовалась.

дополнительные условия это преимущество, так как дают наглядность

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

вместо человекопонятного "равно" надо писать

Сказал человек который только что цеплялся к буквальным значениям употребляемых мной слов. В подавляющем большинстве случаев взаимодействие с NULL выражается в простых аглийских "IS NULL" и "IS NOT NULL".

При этом случаи когда требуется неразличимость сами не выглядят естественными с точки зрения языка. Взять тот же пример снизу. Как будет в вызывающем коде выглядеть функция делающая запрос, в котором имя может сравниваться и со строкой, и с NULL? "findUsersByName(null)"? С точки зрения естественности гораздо лучше когда для нахождения записей с отсутствующими именами есть отдельная функция findUsersWithoutName() и отдельный запрос.

С моей точки зрения эта цитата подтверждает мои слова, а не ваши

Семантика самого NULL изначально подразумевает что будет возвращен NULL в результате почти любой операции с ним. Поэтому то что NULL слева и NULL справа имеют одинаковую семантику ничего собственно не меняет.

Я вам всю ветку это и объясняю, что неправильно называть сравнение NULL в поле "цвет" сравнением значений цветов, потому что NULL это не значение цвета.

А я вам всю ветку объясняю, что NULL в SQL и NULL в императивных язвках это совершенно разные вещи, у первой все правильно, и у второй все правильно.

В смысле?

Я написал это после процитированного вами предложения.


— я предлагаю считать, что в строке есть признак "значение отсутствует"
— признак это не само значение… Собственно, именно так оно сейчас и есть.
— Нет, сейчас есть не так. Сравнение двух признаков со значениями true или false дает определенный результат, а не неизвестность


Сравнение двух признаков дает true или false, сравнение двух NULL сейчас дает "неизвестность", следовательно, сейчас "не так", то есть работает не как сравнение признаков.


Пишем "==" — сравниваем значения

Признак "значение отсутствует" это тоже значение типа bool с возможнымим вариантами true и false, специальный оператор для сравнения этих значений не требуется.


А оператор "==" совершенно точно предназначен только для значений. Которых в нашей системе точно нет.

Ну так то, что их нет, задается другим значением true или false. И вот эти другие значения можно прекрасно сравнить между собой.
Значение в поле типа INT NULL хранится так. Есть 4 байта на INT, и кроме них есть специальный бит is_null, в котором записано значение 0 или 1. Логически это полный эквивалент двух не-null полей BOOLEAN+INT.


Ну и в-третьих из необходимости изменений никак не следует что теория не использовалась.

Из необходимости изменений следует, что выбор был недостаточно обоснован.


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

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


вместо человекопонятного "равно" надо писать
Сказал человек который только что цеплялся к буквальным значениям употребляемых мной слов.

Я вам уже пояснил в предыдущем комментарии, почему это не буквоедство.
И мне непонятно, где вы тут увидели противоречие в моих словах. Вы можете сформулировать его словами, а не намеками? Я как раз за то, чтобы везде сравнивать на равенство оператором, буквальное значение которого это "равно".


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

Я не понял, как эта фраза связана с текущим обсуждением. Взаимодействие со словом "null" в английском? Там и с "one" взаимодействие идет через глагол "is". Взаимодействие с NULL в БД? Так о том и разговор, насколько оно логично, оно не может являться аргументом, подтверждающим самого себя.


Как будет в вызывающем коде выглядеть функция делающая запрос, в котором имя может сравниваться и со строкой, и с NULL? "findUsersByName(null)"?

Именно так и будет выглядеть. У нас 1000 функций, а вы предлагаете делать на каждую дубль специально для null. А если там 2 аргумента, каждый их которых может быть null, будем 4 похожие функции копипастить?


отдельная функция findUsersWithoutName()

Я бы предположил, что она ищет записи с пустой строкой, когда юзер не заполнил имя.


Семантика самого NULL изначально подразумевает что будет возвращен NULL в результате почти любой операции с ним.

Ну так о том и разговор, что это нелогично и не дает никаких преимуществ ни в каких запросах.


А я вам всю ветку объясняю, что NULL в SQL и NULL в императивных языках это совершенно разные вещи

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

В общем не буду с вам спорить. Вам остается только страдать от того что теория баз данных, SQL, и все реляционные СУБД "неправильные".

Эм, высказать несогласие это означает страдать? Если кто-то когда-то что-то придумал, то всё, критику этого никогда нельзя высказывать? Это странная и непонятная для меня точка зрения. Но дело ваше.

высказать несогласие это означает страдать?

Нет. Страдать означает страдать от того что что-то "неправильное" никогда не будет исправлено в обозримом будущем, и им придется пользоваться в том "неправильном" виде, в котором оно есть.

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

СУБД это инструмент. Всякий инструмент должен по возможности обладать простой, интуитивно человеку близкой и непротиворечивой логикой. Как говорится, удобно лежать в руке. И без дурной самодеятельности типа порешать за пользователя, чего он на самом деле хочет.

Когда мы делаем select mname from persons, появление строчек с NULL нас не удивляет и не раздражает. Когда делаем group by mname, все NULLы собираются в одну строчку. По логике "все NULLы равны". И это нас тоже не раздражает и не удивляет. Операция, обратная группировке - расшифровка (drilldown), когда мы берём конкретное значение и говорим select * from persons where mname =... эээ, стоп. Сработает для всего, кроме NULLа. Потому что инструмент считает, что искать отсутствующее значение это абсурд, и пользователь не должен такого плохого хотеть.

БД это не модель реального мира. Это способ хранить байтики так, чтобы их было легко и быстро вытаскивать. Очень прагматическая задача, для решения которой SQL - рабочий инструмент, который должен удобно лежать в руке и не пытаться нас, кожаных мешков, обдурить.

А можно пример запроса, в котором действительно оказывается удобно, что ((null = null) is true) даёт false?

Любой запрос ипользующий JOIN, например. Причем не обязательно внешний, как писали выше, INNER JOIN тоже будет мусор выдавать.

Не надо слов. Покажите мне код.

CREATE TABLE foo (x int);
CREATE TABLE bar (x int);
INSERT INTO foo (x) VALUES (1), (2), (NULL), (NULL), (NULL);
INSERT INTO bar (x) VALUES (1), (2), (NULL), (NULL), (NULL);
SELECT * FROM foo INNER JOIN bar ON foo.x = bar.x;

Сейчас результатом будут ожидаемые две строки. А если поменять поведение NULL, то результатом будут эти же две строки, а после них девять строк c NULL. Попробуйте все NULL в этом коде заменить на 5, например, и посмотрите сами.

Попробуйте все NULL в этом коде заменить на 5, например, и посмотрите сами.

А вот действительно, если колонки не nullable, и мы джойним по колонкам, в которых повторяются значения, кого будем ругать за то, что у нас вылезло 11 строк? Самих себя, или потребуем, чтобы операция сравнения "5 = 5" давала бы какую-нибудь хтонь, которая бы конвертировалась в булево как false?

LEFT JOIN постоянно делается по колонкам, в которых есть NULL. По сути, это главный его юзкейс. И в нем будет такой такой же эффект. Вы сейчас буквально просите сломать все запросы сложнее хелло-ворлдов. И все это только ради того, чтобы не писать "is" или "is not distinct from" вместо "=", которые нужны не сказать что повсеместно, и это не то чтобы большое отличие.

Всё в порядке с хелловорлдами. Основной юзкейс лефт-джойна - хождение между табличками по внешним ключам. Если мы идём в прямом направлении, у нас nullable слева, но not null справа (потому что там это первичный ключ). А если идём в обратном направлении, первичный ключ у нас слева.

Если ради чего-то мы используем в джойне nullable и слева, и справа, то почти наверняка будем ожидать, что дублирующиеся NULLы будут вести себя так же, как дублирующиеся пятёрки. По крайней мере у меня с ходу не получается выдумать разумный пример для обратного.

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

Интересно, кто что себе покупает на ДР.

Типа: ON user.id = order.user_id AND user.bdate = DATE(order.paid_at).

В реальности я бы скорее смотрел заказы за неделю до дня рождения, т. е. BETWEEN bd - INTERVAL 7 DAY AND bd, и всё бы тоже прекрасно работало.

Интересный пример. В этом случае следование стратегии "NULL=NULL" даст хоть и неправильный, но объяснимый результат. Ситуация, когда не отфильтровали пустые значения и получили гору мусора на выходе, не редкость и сейчас. Троичная логика от этого не защищает.

ON user.id = order.user_id AND user.bdate = DATE(order.paid_at)

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


Если нам нужны записи, у которых указана дата заказа и дата рождения пользователя, можно так и написать WHERE user.bdate != NULL AND order.paid_at != NULL, а не использовать хитрые хаки с отсечением части результатов в джойне. Так сразу понятно, что мы хотим получить.

Да хоть во WHERE, не нужны дополнительные проверки на NULL, потому что то же равенство даст тот же NULL и строки не будут выбраны

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

Насчёт правильного условия в JOIN — согласен, сравнение дат лучше во WHERE.

Тем не менее, для меня очевидно и ожидаемо, что WHERE user.bdate = DATE(order.ctime) отбросит нулевые значения.

Это для вас очевидно, потому что вы его писали. А завтра в выводимых данных найдут баг, и другому программисту придется разбираться в вашем запросе, и он не знает, какие из всех возможных побочных эффектов вы использовали намеренно, а про какие не подумали, из-за чего и возник баг.


Даже в описании задачи вы пишете "у некоторых есть дата рождения", ну так почему бы в формальной записи тоже это не написать. Формальная запись это модель задачи. Чем ближе модель к требованиям, тем проще ее поддерживать, в том числе при изменениях требований.

Для меня это очевидно не из-за того, что я это писал, а благодаря тому, что я давно привык к подобному поведению SQL и ожидаю именно его. Это не побочный эффект и не баг, это часть стандарта.

Что касается моделирования задачи, то условие «у некоторых пользователей есть дата рождения» полностью соответствует nullable-колонке в таблице с пользователями, разве нет?

привык к подобному поведению SQL и ожидаю именно его

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


разве нет?

Соответствует, я ничего не говорил против этого. У нас же разговор про запрос, а не про схему данных.

другой программист не знает, какие из всех возможных поведений вы использовали намеренно, а про какие не подумали

Кажется, этот аргумент применим вообще к любому мало-мальски нетривиальному коду.

У нас же разговор про запрос, а не про схему данных.

Задачу я сформулировал следующим образом (и как бы мне теоретически мог сформулировать её коллега-маркетолог): "Интересно, кто что себе покупает на ДР", и условие в запросе в точности соответствует постановке.

этот аргумент применим вообще к любому мало-мальски нетривиальному коду.

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


и условие в запросе в точности соответствует постановке.

Нет, условие в запросе не показывает, что нам нужны только те записи, у которых указана дата рождения и дата оплаты. А еще может быть так, что по историческим причинам у нас есть даты вида "0000-00-00", и в ваш запрос они попадут, и все равно придется добавлять для них дополнительное условие в WHERE. Логичнее все-таки выглядит, когда и NULL и "0000-00-00" отфильтровываются явно, а не когда для одного есть фильтр, а для другого нет, при этом они оба отфильтровываются.

Вроде в стандарте скуэльэтот тип данных назывался unknown и сразу все было ппонятно почему нельзя сравнивать неизвестные значения.

А почему нельзя? Неизвестность и есть неизвестность

Я, конечно, вообще ничего не понимаю в базах данных, но по-моему, в примере отчество Джона Смита не null а эмм... NationallyTraditionallyNotApplicable и его не надо делать NULL и на уровне приложения надо обрабатывать ситуацию NationallyTraditionallyNotApplicable, правда же? Просто это лишние телодвижения, неопределенность, потому что этих самых национальных традиций именования людей миллион вариаций, и никто не будет этим заморачиваться. Зато так у нас никакой троичной логики, а просто множество дополнительных двоичных веток с вариациями национальных имен. Правильно я понимаю?

Как мне кажется, замена задачи "ФИО/ФИ" на " NationallyTraditionallyNotApplicable" всего лишь передвинет вопрос NULL в другую часть предметной области, плюс неоправданно усложнит манипуляцию с данными.

Ну и нужно понимать, что автор выбрал пример "ФИО/ФИ" ради иллюстрации своих рассуждений про NULL, а не наоборот.

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

Ключевой момент тут в том, что NULL не участвует в релейшенах. По хорошему, это, конечно, должно быть не одно захардкоженное значение, а enum с перечислением значений не допускающих джойны. Что-то типа:

CREATE TABLE foo (x int | #0 | #broken | #unknown | #meaningless | #secret);

Да, тут ещё и ноль не будет джойниться.

Наверное, то, о чём вы говорите, называется Null Object. В Python сделано примерно так, там None == None` даёт True.

Ну вот да, в Питоне None==None, и никто горя не знает. А совет использовать там "is" вместо "==" при проверке на None исключительно потому, что некоторые всратые библиотеки могут переопределять оператор равенства для своих объектов, и делать это неочевидным образом. Например, могут упороться и применить троичную логику.

Дело даже не в равенстве как таковом, а в том, что переменная со значением None – это не пустая ссылка, а ссылка на объект, который ведёт себя как объект. Например, реализует методы. Сравнение, например.

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

Требовать от SQL поведения аналогичного императивным языкам - очень странно. Например, в SQL неопределён порядок вычисления булевых выражений. Нужно объяснять, почему так сделано?

UFO just landed and posted this here

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

UFO just landed and posted this here

По стандарту SQL, все части предиката будут вычислены. Поэтому обычно в SQL условия типа (N - число) AND (to_number(N) > 5) приведут к ошибке. По опыту, даже оборачивание первого условие внутрь подзапроса не всегда поможет. Хотите жесткой определённости последовательности вычислений? Есть вариант с использованием оператора case, но только, например, в PostgreSQL если аргумент - литерал или входящий параметр (который также может считаться литералом, т.к. первоначально учитывается при планировании), а функция детерминирована вообще (immutable) или в пределах транзакции (stable), то планировщик может попытаться её вычислить даже если она находится в неработающей ветке (просто потому что так работает последовательное упрощение дерева вычислений).

UFO just landed and posted this here

Троичная логика не применяется нигде кроме вычисления условий. Вне условий результат UNDEFINED интерпретируется как FALSE. Даже внутри самого SQL.

Увы Вам. Будучи полученным в CHECK constraint, NULL разрешает создание/изменение записи, т.е. интерпретируется как / выполняет тот же выбор, что и TRUE.

Хахаха, больше безумия богу безумия

в данном случае работает логика "мы не можем проверить эту строчку, поэтому пропускаем check". Нужно другое поведение? Никто не мешает использовать coalesce.

Ну или другая интерпретация: check constraint проверяет строку на соответствие условию (expr) = false. При выполнении - кидает ошибку.

Вообще, в SQL достаточно странностей (например - работа типов varchar(n)), но едва ли к странностям можно отнести работу NULL-ов в булевых выражениях.

Насколько мне известно, не существует ни одного рационального аргумента в пользу того, что результат сравнения "NULL = NULL" должен быть чем-то кроме TRUE. 

Нет, NULL - это неопределенность, например, в булевом поле это означает: "пока нет данных, 0 это или 1". Поэтому два NULL могут оказаться разными, а могут и одинаковыми (поэтому - неопределенность, NULL при сравнении NULL == NULL).

Далее NULL is NULL = True, то есть на вопрос "это неопределенность?" отвечаем, видя NULL - да, это проклятая неопределенность!

А or B, если А=1, будет 1, даже если B неопределено (каким бы оно не было).

А or B, если А=0, будет NULL, даже если B неопределено (то есть на месте В возможен и 1 и 0).

А and B, если А=0, будет 0, при любом, даже неопределенном В

А and B, если А=1, будет NULL, поскольку возможны оба варианта.

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

NULL - это неопределенность

Далеко не всегда. Ещё Кодд писал про то, что нам нужно два отдельных NULLа - тот, который неизвестность и тот, который "атрибут неприменим". Вес услуги или нематериального товара, расход топлива в литрах для электромобиля, дата смерти живого человека. Но идея Кодда народу не зашла, и даже примерно понятно почему.

Вообще, обстоятельств, почему клеточка в таблице осталась пустой, сильно больше, чем два. Кодировать их отдельными null-like значениями (null для одного, undefined для другого, void для пятого, empty для десятого) - только всех запутать. Одного NULLа достаточно, а если действительно будет нужно уточнение, мы точно придумаем, как его записать в базу.

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

Да, как в примере с выносом фамилии, имени и отчества в отдельные таблицы, где атрибуты будут not null, но в результатах запроса всё равно будем иметь тот самый null.

А если вместо null иметь в таблице запись "-" (прочерк), тогда во первых значение отчества будет точно определено - отчества нет, его не забыли написать, просто нет.

Прочерк - это определенное состояние, т.е. вы точно знаете, что отчества нет. А NULL - неопределенное, когда вы ещё не спросили Джона, есть ли у него middle name

Вписывание "специальных" фейковых значений - очень плохая практика.

UFO just landed and posted this here

пока нет данных, 0 это или 1". Поэтому два NULL могут оказаться разными, а могут и одинаковыми

Нет же, как только появляются данные, они перестают быть NULL. А пока "неизвестно" - нам одинаково неизвестно про любую переменную, что там в ней.

Пустая коробка и коробка с неизвестным рычащим зверем внутри - две большие разницы.

Насколько мне известно, не существует ни одного рационального аргумента в пользу того, что результат сравнения «NULL = NULL» должен быть чем-то кроме TRUE.
Что есть рациональный аргумент? Вот у нас есть
HAS_DOOR boolean
DOOR_COLOUR colour
Можно ли сказать, что все дома без дверей имеют одинаковый цвет двери? Имхо тут найдётся аргументация для противоположных ответов, и, соответственно, для противоположных взглядов на вопрос равенства NULL друг другу.

Если придерживаться догмы, то да. Но когда дело дойдёт до дела, равенство "никаких" цветов между собой будет наименее удивляющим поведением системы.

Вообще-то про цвет отсутствующих дверей можно говорить что угодно.

Что все отсутствующие двери одного цвета, либо у каждой отсутствующей двери уникальный цвет.

Эти утверждения непроверяемые.

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

UFO just landed and posted this here
Можно ли сказать, что все дома без дверей имеют одинаковый цвет двери?

А они не имеют одинаковый цвет ни в каком смысле. Типы "сolour null" и "colour not null" отличаются тем, что для null есть отдельный скрытый флаг. Вот он и показывает признак "запись имеет свойство цвет". Когда этот флаг true, это означает, что запись информации о цвете не имеет, и то значение, которое записано в соседних байтах где по схеме хранится фактический colour, не имеет смысла, и учитывать его не надо.


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

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

Пофиксил, не благодарите.

Утверждение "у этого дома нет цвета двери и у того дома нет цвета двери, в этом они одинаковы" выглядит бредово, а утверждение "у этого дома нет цвета двери и у того дома нет цвета двери, в этом они различаются" выглядит не бредово? Не, это чушь какая-то.

Обратите внимание, что в обоих ваших утверждениях нет сравнения по цвету ("у этих домов одинаковые цвета дверей" или "left.doorcolor === right.doorcolor"), но есть сравнение по наличию признака ("left has-attribute doorcolor === right has-attribute doorcolor" или "left.doorcolor is NULL and right.doorcolor is NULL").

Обратите внимание, что в обоих ваших утверждениях нет сравнения по цвету

Естественно, я про это и сказал. "для null есть отдельный скрытый флаг. Вот он и показывает признак "запись имеет свойство цвет"".

А зачем вы сравниваете null с boolean-значениями?

Null идеально подходит для обозначения отсутствия внешней связи. Используйте его по назначению, и тьма рассеется.

Домен boolean-значений такой же, как и все другие, в том числе и "сущностные" домены. Не хуже и не лучше. И boolean-колонка это relation между доменом первичного ключа и boolean-домена. В случае отсутствия relation будет честный null.

(впрочем, это не отменяет того, что за nullable boolean колонки нужно бить по рукам)

(впрочем, это не отменяет того, что за nullable boolean колонки нужно бить по рукам)

Почему? Жили у нас клиенты в БД.

В каком то году, государство обязало нас уведомлять их о каких-то специфических вещах, из разряда "примите новые условия обслуживания".

И наивная реализация будет как раз nullable boolean - клиент либо ещё не заходил и его решение - null, либо зашел и решил будет он пользоваться дальше (true), либо нет (false).

Быстрая, наивная и вполне удобная реализация.

Да, можно завести отдельную табличку, создавать в ней записи, наличие записи - факт действия пользователя, все дела. Но обязательно ли это? Очевидно, нет.

Поддерживаю: банальная задача - Таблица А со справочником товаров и таблица В с весогабаритными характеристиками товаров, связаны с собой по артикулу товара. Поиск товаров, которые есть в таблице А, но не имеют описания ВГХ в таблице В, происходит элементарным лефт джойном по условию "артикул товара в таблице В как раз NULL".

Ну так это концептуально неправильная реализация, вообще говоря. NULL слишком перегружен смыслами. Означает ли NULL, что мы не знаем, что решил клиент? Нет, тут смысл вполне конкретный: клиент ещё не сделал решения. Собственно, все три варианта вполне себе покрываются отдельным типом:


enum ClientDecision {
    NotDecided,
    Agreed,
    Disagreed,
}

И для значения этого типа факт равенства одного NotDecided другому — абсолютно логичная вещь.


Но, разумеется, пользовательские сумм-типы — это слишком сложная вещь для SQL. 😒

Означает ли NULL, что мы не знаем, что решил клиент?

И это решение программиста\бизнеса. В данном случае - да, клиент ещё не сделал решения.

Делать отдельный тип на то, что прекрасно закрывается стандартным типом - оверинжениринг.

ПС: навскидку кстати не уверен, даже если делать тип и прочее - по моему скулю дешевле будет создать колонку со значениями в null, чем колонку со значениями в NotDecided, что на большой БД может быть существенно.

Делать отдельный тип на то, что прекрасно закрывается стандартным типом — оверинжениринг.

прекрасно

Вовсе нет. Как вы зафиксируете семантику NULL для этой колонки? БД об этом ничего не знает. И, как я уже сказал, в данном случае неравенство NULL самому себе бессмысленно.

Я, честно, не понял вопроса. Что такое семантика null для колонки?

И зачем нужно иметь какой то смысл в "неравенство NULL самому себе" тоже не понял.

А также: NotYetQuestioned, StillThinking, DataLostOnTheWay, PodpisNerazborchiva. Но если бизнесу важно только знать - получен ли определенный ответ, и если да, то какой - тогда NULL, TRUE, FALSE.

Вообще тут описаны просто три равноправные состояния документа, и кошерно будет поле CHAR (1) со значениями вроде Y/N/U.

Есть ли у вашего предложения плюсы перед nullable boolean? Потому что я вижу только (из небольших минусов) необходимость работы с константами в процедурах либо на бэке (этими самыми YNU) и необходимость явно заполнять дефолтное значение, что на больших объемах тоже не бесплатно.

Тут нет дефолтного значения, это фактический статус документа, который нужно как-то определить (узнать). Эти три значения сами по себе не исключают возможности использования и NULL тоже, как признака неизвестности.

Так я неспроста описал бизнес кейс в первом посте.

Какая бизнесовая разница у вас между null и U?

И ещё раз, вопрос был - в чём плюс вашего предложения с char.

U - о документе известно, что он не подписывался. NULL - нет информации о статусе документа (например, в нашу базу интегрируются данные нового Урюпинского дочернего предприятия, которые до того велись в Урюпинске нерегулярно и на бересте).

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

Так с точки зрения бизнеса оно равноценно же.

В случае пока у клиента null или U - мы ему предлагаем ознакомиться с новыми условиями и принять или отказаться.

Т.е. с точки зрения поведения нашей системы - нет разницы, нет смысла делать так.

Если он уже подписал или отказался, надо выяснить статус, а не повторно предоставлять. Иначе получится, что он может два раза сделать разные выборы, что приведёт к юридической коллизии.

Сценарии тяжело обсуждать вне конкретики. Но и при проектировании БД редко мы знаем всю конкретику досконально. Я бы не взял ответственность объединять эти случаи на уровне хранения данных.

Идея для стартапа: форкнуть Постгрес и сделать Postgres BFE (Bullshit-free Edition). И посмотреть, как народу зайдёт такая еретическая идея.

Рвение ценю, но Postgres — та ещё хтонь, её невозможно развивать в одиночку.


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

Я вам страшную вещь скажу, то и другое равномощно машине Тьюринга.

Вот чего автор привязался к троичной логике в SQL-92, она работает нормально, и вполне себе используется теми, кому она нужна )
Предлагаю- следующую тему для систем на COBOLе пусть обсудит. Вот это гораздо интереснее (нет).

Очень хотелось бы посмотреть на пример запрса, в котором троичная логика оказывается полезна. Вот пример, где она мешает:

select *
from persons
where mname = :MName
  or mname is null
    and :MName is null

где :MName это подставляемый из переменной параметр.

Мерзкий костыль, разве нет? Конструкции, подобные этому "or ..." приходится городить постоянно. Особенно оно доставляет в сложных запросах с множеством джойнов и нетривиальными условиями. Обход этой дебильной "фичи" уже давно доведён до автоматизма, но всё равно время от времени где-нибудь да словишь западло. Это как головная боль, к которой невозможно привыкнуть.

Всё ещё надеюсь увидеть пример запроса, где троичная логика полезна, но пока что мне что-то подсказывает, что жду зря.

У вас неправильно спроектирована база данных, в таком случае целесообразно использовать пустую строку, а не null.

Так лучше?

select *
from persons
  left outer join person_mnames
    on person_mnames.person_id = person.id
where person_mnames.mname = :MName
  or person_mnames.mname is null
    and :MName is null

В классическом SQL времён коддовской DB2 вообще не было операций outer join. Будьте проще, и люди к вам потянутся, а таблицы будут нормализованы.

Хахаха, когда-то и телефоны только по проводам были.

Напомнило ситуацию, когда для поля person_mnames.mname надо было различать NULL, хранящийся в приджойненной записи, и NULL, возникший из-за неприджойненной записи.
Сумятицы добавляет тот факт, что в некоторых СУБД пустая строка тождественна NULL, а в некоторых это разные вещи.

Это не везде работает. В Посгресе, например, если параметр не null, не true и не false, отвалится с ошибкой.

Вот так в постгресе точно должно работать:

select * from persons where mname is not distinct from :MName;

Прикольно. В Постгресе "1 is 1" не работает, а "1 is not distinct from 1" работает. А в SQLite наоборот.

В любом случае 99.9% из миллионов тех, кто пишет на SQL, голосуют ногами за "=". Потому что всякое такое "is not distinct from" это долбаная экзотика, даже не имеющая единообразной поддержки со стороны основных игроков.

Ну ладно, с равенством (пардон, с нотдистинктностью) разобрались. Что будем делать с другими парами операторов сравнения, от которых нам нужна работа по закону исключённого третьего, а мы получаем в лицо закон исключённого четвёртого? Я имею в виду меньше/не меньше и больше/не больше?

А как по-вашему 1 > NULL — это истина или ложь? А 1 < NULL?

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

Как ещё предложите обрабатывать такие случаи?

С точки зрения Мировой Истины, Сияющей, Вечной и Неизменной, вопрос "что больше, 1 или неизвестно сколько", смысла не имеет. Но когда мы сортируем nullable-колонку по возрастанию, "nulls first" является наименее удивительным поведением. Соответственно, если по убыванию, то "nulls last". Именно поэтому "NULL < 1" должно давать true.

Можно, конечно, сделать наоборот. С позиции Сияющей Истины пофиг. Но с точки зрения least astonishment совсем не пофиг.

А если нужен nulls last? Тут больше проблема в слабой типизации. По хорошему программист тут обязан сделать явное приведение обоих операндов к единому типу, а не полагаться, что свои иррациональные ожидания.

Тогда, как и раньше, пишем order by ... nulls last.
Фишка в том, чтобы применяемое по умолчанию упорядочивание стопроцентно соответствовало логике операций сравнения на больше-меньше. Иначе западло и источник багов.

А вы географические координаты как будете сравнивать на больше/меньше?

Понимаете ли, нам совершенно плевать на глобальную экзистенциальную истинность того, что вот эта пара координат больше вон той другой. Нам всего лишь нужно отношение упорядочивания, которое работает понятным, единообразным и неабсурдным образом.
Вот, например, кому-нибудь плохо от того, что "Больше" < "Меньше" даёт true?

В реальной жизни "закон исключённого третьего" работает примерно никогда, так как почти всегда есть та или иная форма неопределённости (не задано, бессмысленно, ещё не загружено, удалено, да и банальное всё не так однозначно). Бинарная логика - это математическая абстракция, работающая только в своём мани-мирке.

У нас и 2+2 это математическая абстракция, живущая только в своём тавтологически самом на себя замкнутом мини-мирке (см. "Логико-философский трактат" Витгенштейна), но это нам не мешает использовать арифметику как простой, понятный, надёжный и полезный инструмент.

Только 2+2 не противоречит реальности.

Иногда противоречит. Хотите пример?

Пример: скидка 2% плюс ещё одна скидка 2% даёт общую скидку 3.96%
При этом наценка 2% плюс ещё наценка 2% будет 4.04% наценки. Получается прямо как в том анекдоте - "а мы покупаем или продаём?"
2+2=4 только когда применяем правило к аддитивным величинам. А вопрос о том, являются ли величины аддитивными, обычно вне математики.

И где вы в реальности проценты нашли? ±2% - это короткая запись для множителя ( 100 ± 2 )/100

Ещё пример. Вчера на улице было 2 десятка градусов, и сегодня 2 десятка. Итого в выходные у нас было 40°С, ага?

А в SQLite наоборот.

В SQLite последней версии работают оба.

В любом случае 99.9% из миллионов тех, кто пишет на SQL, голосуют ногами за "="

Где можно посмотреть результат опроса? В частности, интересно было бы посмотреть на распределение ответов по учусь-джун-миддл-сениор.

"is not distinct from" это долбаная экзотика

Это фича из стандарта SQL которой десятки лет, за отсутствие которой можно справедливо пинать разработчиков СУБД.

Где можно посмотреть результат опроса?

99.9% это нижняя оценка. За 25 лет работы с SQL я ни разу нигде и ни у кого не встречал это самое "is not distinct from".

В некоторых СУБД можно так:
select * from persons where mname <=> :MName

С темой статьи согласен, но вообще обычно это делается на уровне приложения в квери билдере, если параметр null, он ставит "is", если не null, ставит "=".

Ага, обкостылить эту дурацкую фичу специальным ифом в коде бэкенда. Хрен редьки не слаще.

А какая вам разница, если вам не надо вручную это писать, ни код бэкенда, ни сам SQL? Поставили через пакетный менеджер и пишете как удобно. Вам важнее понятность кода вашего приложения или количество ифов где-то внутри сторонней библиотеки?

Речь о всяких вещах типа Dbeaver?

Некоторые ORMки сами умеют это обкостылевать, некоторые (например, SQLAlchemy) нет. И я не уверен на сто процентов, действительно ли я хочу, чтобы ORM за меня додумывал этот момент.

Sign up to leave a comment.

Articles

Change theme settings