К написанию этой заметки меня побудили два обстоятельства.
Во-первых, как показывает практика общения с многочисленными пользователями SQL-ориентированных СУБД, многие из них плохо понимают или недостаточно серьезно относятся к проблеме представления в базе данных и обработке неопределенной информации. Во-вторых, на меня произвела впечатление дискуссия, проходящая с 1996 г. на страницах журнала "Database Programming and Design" между всемирно известными экспертами и авторами Крисом Дейтом (Chris Date) и Томом Джонсоном (Tom Johnson), в которой обсуждаются альтернативные пути решения этой проблемы.
Коротко напомним:
- суть проблемы;
- подход, принятый в языке баз данных SQL-92;
- критические замечания Дейта и предлагаемый им альтернативный подход.
Проблема в целом связана с чисто практическими потребностями хранить в базах данных не только достоверную и полностью определенную информацию, но и такую, которая частично неточна или неопределена. Приведем несколько характерных примеров. Может оказаться так, что при приеме служащего на работу ему сразу не выделяется определенное задание. Тогда в записи об этом сотруднике поле "выполняемое задание" (по крайней мере, временно) будет содержать неопределенное значение. Другой пример. По не интересующим нас причинам принимается решение хранить в одной таблице информацию о разных летательных аппаратах. Понятно, что большая часть характеристик относится к крылатому летательному аппарату любого типа. Но поле "мощность двигателя" может содержать содержательные данные только для тех аппаратов, у которых имеется двигатель. Для планера такое поле может содержать только неопределенное значение. Наконец, третий пример. В таблице учета военнообязанных для некоторых лиц может отсутствовать точная дата рождения (она пока неизвестна). Но в данном случае полная неопределенность отсутствует, поскольку известно, что любое допустимое значение этого поля относится к известному диапазону.
Эти три примера иллюстрируют три наиболее распространенных случая неполноты информации. Их называют случаями неизвестности, неприменимости и возможности (того или другого, но неизвестно точно, какого) значений.
В стандарте языка SQL-92 выделяется одно специальное значение NULL, которое считается автоматически входящим в любой встроенный тип данных и любой определенный пользователем домен. Что имеется в виду под неопределенным значением в каждом конкретном случае, определяется семантикой предметной области. В языке фиксируется лишь следующее. Если при вычислении арифметического выражения хотя бы одно из используемых значений есть NULL, то и значение всего арифметического выражения есть NULL. Если в операциях сравнения a op b (op = '=', '<', '>', '<=', '>=') значение левого или правого операнда (или и того, и другого) есть NULL, то истинностным значением операции сравнения является unknown. Тем самым, в SQL-92 используется не двузначная, а трехзначная логика. Таблицы истинности основных логических операций расширяются следующим образом:
true AND unknown = unknown false OR unknown = false NOT(unknown) = unknown
При этом, если логическое выражение представляет собой условие выборки строки из таблицы, то будут выбираться только те строки, для которых значение условия выборки вычисляется в true.
Основными доводами в пользу использования неопределенных значений и трехзначной логики являются следующие. Во-первых, наличие выделенного значения NULL в некоторой степени решает проблему представления неполной информации. Во-вторых, трехзначная логика позволяет в ряде случаев не заботиться о наличии неопределенных значений при формулировке запросов. Как принято говорить, неопределенные значения и трехзначная логика позволяют повысить выразительную мощность языка запросов.
Позиция Криса Дейта, который является давнишним противником использования трехзначной логики в языках баз данных, главным образом основана на опровержении этих доводов. Если вернуться к рассмотренным ранее примерам, то можно увидеть, что только в первом случае приведенные правила вычисления арифметических и логических выражений являются бесспорными. Если, например, обозначить через NULL1 специальное значение, относящееся к случаю неприменимости, то, наверное, более естественно было бы считать, что (NULL1 = NULL1) = true, а (a = NULL1) = false для любого значения a, являющегося полностью определенным. Если обозначить через NULL2 специальное значение, относящегося к случаю возможности, и считать, что диапазон возможных значений соответствующего столбца есть (a, b), то, очевидно, значение условия NULL2 BETWEEN a AND b есть true. И так далее. Другими словами, интерпретация в языке SQL неопределенных значений и возникающей по причине их наличия трехзначной логики хорошо соответствует только одному случаю неполноты информации.
Как полагает Дейт, единственный способ полного решения этой проблемы на основе многозначной логики мог бы состоять в том, что для начала в язык были бы введены три специальных значения с разным смыслом (например, NULL, NULL1 и NULL2) и использовалась бы не трехзначная, а пятизначная логика. Если бы в будущем оказалось, что не учтен еще один специальный случай неполноты информации, то набор специальных значений и число логических значений расширялся бы соответствующим образом. Учитывая, что у пользователей часто возникают затруднения при работе даже и с трехзначной логикой, можно с достаточной уверенностью заявить о практической неприменимости такого подхода.
По мнению Дейта, сложность использования трехзначной логики и ее несоответствие практическим потребностям вызывает необходимость выработки другой схемы представления неполной информации, при которой сохранилась бы возможность применения привычной булевской двоичной логики. В течение долгого времени Дейт пытается разработать такую схему, и его последние предложения стали предметом обоснованной критики в недавней заметке Тома Джонсона (http://www.dbpd.com/9802xtra.htm; обзор этого материала можно найти на сервере http://www.citforum.ru).
Основная идея Дейта заключается в том, что вводится некоторое специальное значение, называемое UNK и обладающее теми свойствами, что (UNK = UNK) = true и (a = UNK) = false для всех значений a, отличных от UNK. По Дейту, другие операции сравнения для UNK бессмыслены. Тем самым, логика остается двоичной.
Том Джонсон приводит два развернутых примера, один из которых демонстрирует, что первое свойство определено неверно, и его использование может привести к фатально неверным результатам при вычислении интуитивно правильно составленного запроса (в случае сравнения значений двух столбцов, содержащих полностью неопределенную информацию). Второй пример показывает, что отсутствие возможности сравнений a > UNK и a < UNK не позволяет воспользоваться имеющейся в базе данных неполной информацией при выполнении запросов типа "а могло бы ли быть так?". (Я не буду более подробно описывать эти примеры; их можно найти по указанным выше адресам.)
Интересно другое. После демонстрации ошибок допущенных Дейтом, Джонсон предлагает краткое изложение своей собственной "практической схемы специальных значений", в которой, по существу, пользователям, не желающим применять NULL и трехзначную логику, предлагается на уровне приложений выделять одно или несколько специальных значений из обычных прикладных доменов. Отличие специальных значений от обычных, фактически, возлагается на прикладную программу. Честно говоря, этот подход заставляет вспомнить старые добрые времена, когда семантика базы данных полностью содержалась в приложениях. Но что самое главное, по-мнению Джонсона, схему SQL-92 с единственным неопределенным значением и трехзначной логикой предлагается сохранить для "рискованных" пользователей, которые постепенно должны научиться в этой схеме работать.
После чтения заметки Джонсона и некоторых раздумий у меня созрела своя точка зрения относительно того, что предоставляет SQL-92 для работы с неполной информацией. На самом деле, это комбинация выразительной трехзначной логики с необходимостью иногда пользоваться значением NULL как явным специальным значением. Трехзначной логики в том виде, в котором она введена в SQL, оказывается достаточно в тех случаях, когда по смыслу значение unknown условия выборки является запрещающим. Другими словами, если пользователя не интересует неполная информация, то автоматическая трактовка NULL при вычислении арифметических выражений и выработка логического значения unknown при вычислении логических выражений действительно позволяет коротко и выразительно формулировать запросы. Но для выборки неполной информации приходится явно пользоваться NULL как специальным значением.
Если вернуться к приведенным в начале этой заметки примерам, то, конечно, в ответ на запрос "выдать имена служащих, выполняющих задание с кодом 'A212'" мы никогда не получим имя служащего, которому еще не выделено задание. Это хорошо, правильно и лаконично. Но чтобы получить имена служащих, которые еще не выполняют никакого задания, мы будем вынуждены воспользоваться специальным условием IS NULL (и двузначной логикой!), представляющим собой, по существу, специальную форму операции сравнения для специального значения NULL. Для второго примера естественно и лаконично формулируются запросы вида "выдать названия летательных аппаратов с мощностью двигателя больше 1000 л.с.". Но если нам захочится получить список безмоторных летательных аппаратов, то снова потребуется применить условие IS NULL (и опять двузначную логику). В третьем примере выразительно формулируются запросы "выдать имена военнообязанных, гарантированно родившихся в 1978 г.", но если вместо "гарантированно" написать "возможно", то и в этом случае потребуется добавить к условию выборки IS NULL.
Таким образом, схема, принятая в языке баз данных SQL, с одной стороны, требует от пользователей осмысленного обращения с трехзначной логикой (что дается далеко не сразу) и не избавляет их от потребности использования не слишком выразительных запросов с условиями на специальные значения. К сожалению, не видно, чтобы эта ситуация изменилась в лучшую сторону в текущем проекте нового стандарта SQL-3. Но эта тема заслуживает отдельного обсуждения.