Menu

SQLite и NULL: IS NULL, COALESCE, IFNULL на практике

Разбираемся, как операторы SQLite ведут себя с NULL: почему = и <> работают не так, как кажется, и зачем нужны IS NULL, IS NOT NULL, COALESCE и IFNULL.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

NULL означает «неизвестно»

Любое другое значение в SQLite - это что-то конкретное: число, строка, блоб. С NULL всё иначе. Это заглушка для значения, которое отсутствует или неизвестно. Эта простая мысль объясняет все странности, которые NULL вытворяет в запросах.

Создадим небольшую таблицу для экспериментов:

Два столбца допускают NULL. У Бориса нет email. У Клео не указан возраст. У Дэна нет ни того, ни другого. Дальше речь пойдёт о том, как корректно запрашивать такие строки и не попасться в ловушку.

Почему = NULL не работает в SQLite

Первое, что хочется написать - WHERE email = NULL. Выглядит логично. И возвращает пустоту:

Ноль строк - хотя у Бориса и Дана почта явно NULL. Дело вот в чём: любое сравнение с NULL даёт в результате NULL, а не true или false. А WHERE в SQLite пропускает только те строки, где условие именно true - NULL под это не подходит, поэтому строка отбрасывается. Вот почему = NULL в SQLite не работает.

Та же ловушка и с <>:

Казалось бы, запрос должен вернуть всех, кроме Ады. А возвращает только Клео. Борис и Дэн, у которых email равен null, выпадают - потому что NULL <> 'ada@example.com' тоже даёт NULL, а не true.

Это самая распространённая засада в SQL. Если запрос внезапно «теряет строки», которых вы там ожидали, - первым делом подозревайте колонку с null.

Используйте IS NULL и IS NOT NULL

Правильный способ проверки на null в SQLite - оператор IS. В отличие от =, он умеет работать с null и всегда возвращает true или false, но никогда не null:

Первый запрос вернёт Бориса и Дэна, второй - Аду и Клео. Операторы IS NULL и IS NOT NULL как раз и созданы для того, чтобы задать вопрос «а это значение вообще есть?». Используйте их везде, где рука так и тянется написать = NULL или <> NULL - это типичная ошибка проверки на NULL в SQLite.

Если же нужно получить «все, кроме Ады, включая записи с неизвестным именем», объедините обе проверки явно:

Теперь в выборку попадают Борис, Клео и Дэн.

NULL заражает арифметику и конкатенацию

Правило «неизвестно» работает не только в сравнениях. Любая операция, в которой замешан NULL, даёт на выходе NULL:

next_year и doubled равны NULL у Клео и Дэна. labelled_age у них тоже NULL - конкатенация строки с NULL даёт NULL, а не 'Возраст: '. Если в колонке может оказаться NULL, а на выходе нужно нормальное значение, это нужно обрабатывать вручную. Как раз для этого пригодятся следующие две функции.

IFNULL: подстановка значения по умолчанию

IFNULL(a, b) возвращает a, если оно не NULL, иначе - b. Это самый простой способ заменить NULL на дефолтное значение:

Борис и Дэн получат (нет email). Клео и Дэн получат 0. Исходные данные при этом не меняются - IFNULL лишь переписывает то, что выводится.

У IFNULL всегда ровно два аргумента. Если запасных вариантов нужно больше - берите COALESCE.

COALESCE: побеждает первое не-NULL значение

COALESCE(a, b, c, ...) перебирает аргументы по порядку и возвращает первый, который не равен NULL. По сути, это IFNULL с любым количеством запасных значений:

Для Ada и Cleo подставляется реальный email. У Boris и Dan поле email равно NULL, поэтому SQLite берёт второй аргумент - адрес, собранный из имени. Если бы и он оказался NULL, сработал бы запасной вариант - 'аноним'.

COALESCE - это переносимый вариант: все крупные СУБД поддерживают его одинаково. IFNULL - это удобное сокращение из SQLite и MySQL, но только для случая с двумя аргументами. По умолчанию используйте COALESCE, а к IFNULL обращайтесь, только когда аргументов действительно два и хочется коротко.

NULL и пустая строка в SQLite - это не одно и то же

Частая путаница: разработчики считают, что NULL и '' взаимозаменяемы. Это не так.

'' - это настоящая строка, просто без символов. А NULL - это вообще отсутствие значения. length('') вернёт 0, тогда как length(NULL) сам по себе будет NULL. И NULL = NULL - это тоже NULL, а вовсе не 1. Именно поэтому в SQLite и существует конструкция IS NULL, а проверка через = NULL не работает.

Если в колонке могут оказаться и '', и NULL, заранее решите, что у вас будет означать «значения нет», и придерживайтесь одного варианта. Иначе в каждом запросе придётся учитывать оба случая - и рано или поздно вы про один из них забудете.

NULL в IN, NOT IN и DISTINCT

Есть ещё пара мест, где NULL подкрадывается незаметно.

IN со списком, в котором затесался NULL, может выдать неожиданный результат - особенно в связке с NOT IN:

Казалось бы, должны вернуться все, у кого возраст не равен 25. Но в ответе - пусто. SQLite разворачивает NOT IN (25, NULL) примерно в age <> 25 AND age <> NULL, а выражение age <> NULL всегда даёт NULL - поэтому всё условие никогда не становится истинным. Лечится это так: нужно отфильтровать NULL либо из самого списка, либо из столбца до сравнения. Именно поэтому = NULL в SQLite не работает - для проверки на NULL всегда используйте IS NULL и IS NOT NULL.

А вот DISTINCT, наоборот, при удалении дубликатов считает NULL-значения равными друг другу:

Получаешь три строки: email Ады, email Клео и одно NULL (сюда схлопнулись Борис и Дэн). С GROUP BY и UNION та же история - они считают все null'ы одной группой, то есть ведут себя ровно наоборот по сравнению с =. В SQL логика тут не везде одинаковая, так что полезно держать в голове, на какой стороне находится каждый оператор.

Короткий чек-лист

  • Для проверки на NULL в SQLite используй IS NULL / IS NOT NULL. Никогда не пиши = NULL.
  • Любая арифметика, конкатенация или сравнение с участием NULL вернёт NULL.
  • Чтобы подставить значение по умолчанию, бери COALESCE(a, b, c, ...). Для случая с двумя аргументами есть короткая форма - IFNULL(a, b).
  • Пустая строка '' - это не NULL. Для каждой колонки выбери что-то одно как обозначение «нет значения».
  • NOT IN (..., NULL) - почти всегда баг. Сначала выкидывай null'ы из списка.

Дальше: сортировка результатов

Когда фильтрация строк под контролем - включая строки с null, - пора заняться порядком вывода. На следующей странице разберём ORDER BY, и у него тоже есть свои взгляды на то, где должны оказаться null'ы в отсортированной выдаче.

Часто задаваемые вопросы

Почему column = NULL в SQLite ничего не возвращает?

Потому что NULL означает «значение неизвестно», а сравнение с неизвестным даёт не TRUE, а тоже неизвестный результат. В итоге WHERE col = NULL не находит ни одной строки - даже тех, где значение реально пустое. Правильный вариант - WHERE col IS NULL. С <> та же история: пишите IS NOT NULL.

Чем IFNULL отличается от COALESCE в SQLite?

IFNULL(a, b) принимает строго два аргумента: возвращает a, а если он NULL - то b. COALESCE(a, b, c, ...) берёт сколько угодно аргументов и возвращает первый не-NULL. То есть IFNULL - это укороченная версия для двух значений, а COALESCE - общий случай и при этом стандартная функция, которая работает почти во всех СУБД.

NULL и пустая строка - это одно и то же в SQLite?

Нет. NULL - это «значения нет вообще», а '' - это вполне конкретная строка нулевой длины. Проверка '' IS NULL вернёт 0 (ложь), length('') равно 0, а length(NULL) сам по себе NULL. Если в столбце могут встречаться оба варианта, придётся либо обрабатывать их отдельно в запросах, либо приводить к одному виду на этапе записи.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ