Объяснение значений NULL в SQL: IS NULL, COALESCE, NULLIF и ловушки, которых следует избегать
NULL вызывает некоторые из самых тихих ошибок в SQL: запросы выполняются без ошибок, но теряют строки или искажают средние значения. Узнайте, как на самом деле ведет себя NULL, на проверенных примерах и как с ним безопасно обращаться.
- NULL означает отсутствующее или неизвестное значение. Это не ноль и не пустая строка.
- Проверьте это с помощью
IS NULL, потому что= NULLникогда ничему не соответствует. - Большинство вычислений с NULL дают NULL, а
WHEREсохраняет только те строки, которые определенно верны. - Агрегаты пропускают NULL, что может незаметно изменить среднее значение.
Запрос выполняется без ошибок и возвращает разумное число, но строки отсутствуют, а среднее значение неверно. Значения SQL NULL очень часто являются причиной. NULL не ведет себя как обычное значение, и это бросается в глаза как новичкам, так и опытным разработчикам.
В этом руководстве показано, как именно ведет себя NULL. Мы выполнили каждый запрос в SQLite 3.50, и показанные результаты получены в результате этого запуска. Большая часть такого поведения соответствует стандартному SQL, и мы отмечаем, где базы данных различаются, например порядок сортировки NULL и обработка пустых строк в Oracle.
Что такое НУЛЬ?
NULL — это маркер, означающий, что значение отсутствует или неизвестно. Это не значение в обычном понимании, поэтому это разные вещи:
| Значение | Значение | Пример столбца «Телефон» |
|---|---|---|
НОЛЬ |
Неизвестно или не указано | Мы не знаем номер |
'' |
Известное пустое текстовое значение | Отправлено как пустое |
0 |
Число ноль | Не актуально для текста, но реально для сумм |
Разница особенно важна для сумм. Бонус 0 означает, что человек ничего не получил. НУЛЕВОЙ бонус означает, что никто еще не знает.
Пример таблицы
CREATE TABLE employees (
id INT, name TEXT, phone TEXT, bonus INT, manager_id INT
);
INSERT INTO employees VALUES
(1,'Meera','555-0101',500,NULL),
(2,'Arjun',NULL,300,1),
(3,'Sara','',NULL,1),
(4,'Ravi',NULL,NULL,2);
Обратите внимание, что у Сары есть пустая строка для телефона, а у Арджуна и Рави — NULL. К этим двум относятся совершенно по-разному.
Тестирование на NULL
Вот ошибку, которую поначалу допускают почти все:
SELECT name FROM employees WHERE phone = NULL;
-- (no rows)
SELECT name FROM employees WHERE phone IS NULL;
-- Arjun
-- Ravi
SELECT name FROM employees WHERE phone IS NOT NULL;
-- Meera
-- Sara
Первый запрос является действительным SQL и не выдает ошибок. Он просто ничего не возвращает, потому что сравнение чего-либо с NULL никогда не является истинным. Сара появляется под IS NOT NULL, поскольку пустая строка является реальным значением. Однако в Oracle пустая строка рассматривается как NULL, что является заметным исключением.
Трехзначная логика
В большинстве языков программирования условие является истинным или ложным. SQL добавляет третий результат: неизвестно. Если вы не знаете значение, вы не можете сказать, равно ли оно 5. И неизвестно, что два неизвестных значения равны, поэтому NULL = NULL неверно. В нашем тесте все они вернули NULL:
SELECT NULL = NULL, NULL <> 1, 1 + NULL, 'a' || NULL;
-- NULL | NULL | NULL | NULL
Комбинирование условий следует строгим правилам. Вот результаты нашего прогона, где 1 — правда, а 0 — ложь:
| Выражение | Результат | Почему |
|---|---|---|
ИСТИНА И НУЛЬ |
НОЛЬ | Зависит от неизвестной части |
ЛОЖЬ И НУЛЬ |
Ложь | Ложь, что бы ни было неизвестное |
ИСТИНА ИЛИ НУЛЬ |
Правда | Правда, каким бы неизвестным ни было |
ЛОЖЬ ИЛИ НУЛЬ |
НОЛЬ | Зависит от неизвестной части |
НЕ НУЛЬ |
НОЛЬ | Противоположность неизвестному неизвестно |
Строки, которые исчезают из WHERE
WHERE сохраняет строку, только если ее условие true. Строки, результат которых неизвестен, удаляются вместе со строками, имеющими ложные значения. Это приводит к удивительным результатам:
SELECT name FROM employees WHERE phone <> '555-0101';
-- Sara
Вы могли бы ожидать появления Арджуна и Рави, поскольку их телефон определенно не с этим номером. Но их телефон неизвестен, поэтому результат неизвестен, и они исчезают. Чтобы включить их, скажите об этом явно: WHERE phone <> '555-0101' OR phone IS NULL. Отрицание тоже не помогает:
SELECT name FROM employees WHERE NOT (bonus > 400);
-- Arjun
У Сары и Рави нет бонусов, поэтому они не появляются ни в bonus > 400, ни в его противоположности. Это ключевой момент, который следует помнить: условие и его отрицание не всегда охватывают каждую строку.
COALESCE: обеспечить запасной вариант
COALESCE возвращает первое значение, отличное от NULL, в своем списке и работает во всех основных базах данных:
SELECT COALESCE(NULL, NULL, 'third', 'fourth');
-- third
SELECT name, COALESCE(phone, 'no phone') FROM employees;
-- Meera | 555-0101
-- Arjun | no phone
-- Sara |
-- Ravi | no phone
Сара по-прежнему отображается пустой, поскольку пустая строка не равна NULL. Подобные функции существуют, например IFNULL в MySQL и SQLite, ISNULL в SQL Server и NVL в Oracle. COALESCE — портативный вариант. Наше предыдущее замечание о расчетах применимо и здесь. bonus + 100 дает NULL для всех, у кого нет бонуса, поэтому напишите COALESCE(bonus, 0) + 100, если считать недостающий бонус нулевым для вашего бизнеса.
NULLIF: превратить значение в NULL
NULLIF(a, b) возвращает NULL, если два значения равны, в противном случае возвращает a. Два варианта использования особенно полезны. Он может очищать значения заполнителей:
SELECT name, NULLIF(phone, '') FROM employees WHERE id = 3;
-- Sara | NULL
SELECT name FROM employees
WHERE COALESCE(NULLIF(phone, ''), '') = '';
-- Arjun
-- Sara
-- Ravi
Этот второй запрос — удобный способ найти значения, которые «отсутствуют или пусты». Он также может защитить от деления на ноль, которое вызывает ошибку во многих базах данных:
SELECT total / NULLIF(quantity, 0) FROM sales;
Когда quantity равно 0, это дает NULL, а не ошибку. В нашем тесте SELECT 100 / NULLIF(0, 0) вернул NULL.
Агрегаты пропускают NULL
Именно здесь NULL наносит наибольший ущерб отчетам:
SELECT COUNT(*), COUNT(bonus), SUM(bonus), AVG(bonus),
AVG(COALESCE(bonus, 0))
FROM employees;
-- 4 | 2 | 800 | 400.0 | 200.0
| Выражение | Результат | Что это значит |
|---|---|---|
COUNT(*) |
4 | Все строки |
COUNT(bonus) |
2 | Только строки с бонусом |
SUM(bonus) |
800 | NULL игнорируются |
AVG(bonus) |
400 | 800 разделить на 2, а не на 4 |
AVG(COALESCE(бонус, 0)) |
200 | 800 разделить на 4 |
Так какой же средний бонус: 400 или 200? Это зависит от того, что означает NULL в ваших данных. Если NULL означает «бонус не был выплачен», то верно значение 200. Если это означает «еще не решено», то 400, возможно, и правы. База данных не может принять это решение за вас, поэтому вопрос должен быть адресован тому, кто разбирается в бизнесе.
Связанная ловушка: SUM без значений имеет значение NULL, а не 0. Когда мы суммировали только строки без бонуса, мы получили NULL, а COUNT вернул 0. Код приложения, ожидающий числа, может привести к сбою на этом, поэтому оберните его: COALESCE(SUM(bonus), 0).
Совет. Если итоговые данные в отчете не соответствуют действительности, сравните COUNT(*) с COUNT(column). Любой разрыв между ними сразу скажет вам, сколько NULL имеет этот столбец.
ГРУППИРОВАТЬ ПО, сортировать и РАЗЛИЧАТЬ
Хотя значения NULL «не равны» при сравнении, эти операции рассматривают их как одну группу:
SELECT bonus, COUNT(*) FROM employees GROUP BY bonus ORDER BY bonus;
-- NULL | 2
-- 300 | 1
-- 500 | 1
SELECT DISTINCT bonus FROM employees;
-- 500, 300, NULL
Дополнительную информацию о группировке см. в разделе SQL GROUP BY и HAVING. Порядок сортировки — это то, где базы данных действительно различаются. SQLite и MySQL рассматривают NULL как наименьшее значение, а PostgreSQL и Oracle — как наибольшее. Таким образом, тот же ORDER BY bonus DESC может помещать NULL последними в один и первым в другой. PostgreSQL, Oracle и современный SQLite позволяют вам заявить об этом напрямую, используя NULLS FIRST или NULLS LAST.
Присоединяется
Условие соединения с использованием = никогда не соответствует NULL, поэтому строки с ключом NULL исключаются из внутреннего соединения:
SELECT e.name FROM employees e
JOIN employees m ON m.id = e.manager_id;
-- Arjun, Sara, Ravi (Meera is gone)
SELECT e.name, m.name FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;
-- Meera | NULL
-- Arjun | Meera
-- Sara | Meera
-- Ravi | Arjun
У Миры нет менеджера, поэтому внутреннее соединение автоматически удаляет эту строку. LEFT JOIN сохраняет его, а также создает собственные NULL для недостающей стороны. Подробнее читайте в объяснениях SQL-соединений.
Ловушка «НЕ В»
Один NULL в списке NOT IN уничтожает все результаты:
SELECT name FROM employees WHERE manager_id NOT IN (1);
-- Ravi
SELECT name FROM employees WHERE manager_id NOT IN (1, NULL);
-- (no rows)
Вторая форма означает manager_id <> 1 AND manager_id <> NULL, и эта последняя часть никогда не может быть правдой. Это особенно опасно для подзапросов, где NULL может прийти незамеченным. Вместо этого используйте NOT EXISTS, как описано в нашем руководстве по подзапросам SQL. Мира также отсутствует в первом результате, поскольку manager_id этой строки имеет значение NULL.
Проектирование с NULL
- Используйте
NOT NULLпо умолчанию. Разрешите NULL только тогда, когда значение действительно может отсутствовать. - Добавьте разумные значения по умолчанию. Например,
quantity INT NOT NULL DEFAULT 0. - Не используйте магические значения. Дата 1900-01-01 или возраст -1, заменяющий «неизвестно», испортят ваши расчеты.
- Знайте поведение UNIQUE. Большинство баз данных допускают наличие нескольких NULL в столбце
UNIQUE, и наш тест вставил два значения без ошибок. SQL Server является исключением, поскольку обычно это допускается. - Помните, что первичный ключ не может иметь значение NULL. См. первичный ключ и внешний ключ.
- Тестирование с NULL. Тестовые данные без NULL скрывают эти ошибки. Включите их намеренно, как и другие крайние случаи.
Итоги
Относитесь к NULL как к «неизвестному», и большая часть его поведения обретает смысл: неизвестно плюс один неизвестно, и неизвестное равно неизвестному, неизвестно, что оно истинно. На этой неделе проведите аудит одного из своих отчетов. Сравните столбцы с нулевым значением, используя COUNT(*) и COUNT(column), найдите любой NOT IN и решите, что NULL должен означать в каждом среднем значении.
Часто задаваемые вопросы
Что такое NULL в SQL?
NULL — это маркер отсутствующего или неизвестного значения. Оно отличается от нуля и пустой строки и требует специальной обработки при сравнении.
Почему = NULL не работает?
Сравнение чего-либо с NULL дает результат «неизвестно», а не «истина», поэтому WHERE отбрасывает строку. Используйте IS NULL или IS NOT NULL, чтобы проверить это.
В чем разница между COALESCE и ISNULL?
COALESCE — это стандартный SQL, принимающий множество аргументов. ISNULL специфичен для SQL Server и принимает два значения. Для переносимого кода отдайте предпочтение COALESCE.
Включает ли COUNT значения NULL?
COUNT(*) подсчитывает все строки, включая строки со значениями NULL. COUNT(столбец) подсчитывает только строки, в которых этот столбец не равен NULL, что может дать меньшее число.
NULL — это то же самое, что пустая строка?
Нет, в большинстве баз данных. Пустая строка — это известное значение без символов, а NULL означает неизвестное. Oracle является заметным исключением, поскольку он рассматривает пустые строки как NULL.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Тестирование ПОПользовательское приемочное тестирование (UAT): процесс, контрольный список и реальные примеры
Приемочное тестирование пользователей объяснило: кто его проводит, чем оно отличается от контроля качества, семиэтапный процесс, шаблон тестового сц…
Читать статью →Базы данных и SQLОбъяснение значений NULL в SQL: IS NULL, COALESCE, NULLIF и ловушки, которых следует избегать
Значения SQL NULL объяснены с помощью запросов, которые мы выполнили: трехзначная логика, IS NULL, COALESCE, NULLIF, как агрегаты, соединения и сорт…
Читать статью →Базы данных и SQLОператор SQL CASE WHEN: синтаксис, реальные примеры и распространенные ошибки
SQL CASE WHEN объяснен с помощью запросов, которые мы выполняли: простые формы и формы для поиска, порядок условий, повороты с помощью SUM, трюки CO…
Читать статью →Разработка ПОЧто такое JWT? Веб-токены JSON, объясненные на рабочем примере
Что такое JWT? Объяснение JSON Web Tokens: три части: токен, созданный и измененный в коде, который мы запускали, JWT и сеансы, хранилище, токены об…
Читать статью →Разработка ПООбъяснение цикла событий JavaScript: стек вызовов, микрозадачи и таймеры с примерами
Цикл событий JavaScript объясняется с помощью кода, который мы запускали: стек вызовов, очереди задач и микрозадач, почему setTimeout 0 не является …
Читать статью →Разработка ПОГенераторы Python и объяснение доходности: экономьте память с помощью реальных примеров
Генераторы Python и доходность, объясненные с помощью кода, который мы запускали: как они приостанавливаются и возобновляются, измеряют экономию пам…
Читать статью →Искусственный интеллектЧто такое вложения? Простое объяснение векторов и косинусного сходства
Что такое вложения? Простое руководство по векторам, косинусному подобию и семантическому поиску с небольшим примером, который мы использовали, общи…
Читать статью →DevOpsСине-зеленое и канареечное развертывание: различия, компромиссы и как выбрать
Объяснение сине-зеленого и канареечного развертывания: как каждый из них работает, стоимость, риски и откат, стабильное группирование пользователей,…
Читать статью →Автоматизация тестированияНенадежные тесты: что их вызывает и как их исправить навсегда
Ненадежные тесты объясняются на демонстрациях, которые мы запускали: основные причины, способы подтверждения и диагностики, правильные исправления в…
Читать статью →Тестирование ПОНагрузочное тестирование и стресс-тестирование: различия, метрики и примеры k6
Объяснение нагрузочного и стресс-тестирования: что доказывает каждое из них, тесты на всплески и выдержки, важные метрики, почему средние значения в…
Читать статью →Тестирование ПОТестовый пример и тестовый сценарий: ключевые различия, примеры и когда использовать каждый
Объяснение тестового случая и тестового сценария: четкие определения, параллельная таблица, одна функция сброса пароля, взятая из сценариев в случаи…
Читать статью →Базы данных и SQLОптимизация SQL-запросов: чтение планов выполнения и исправление медленных запросов с указанием времени
Оптимизация SQL-запросов проверена на миллионе строк: читайте планы выполнения, добавляйте правильные индексы, исправляйте неразмещаемые фильтры, ис…
Читать статью →Базы данных и SQLSQL UNION против UNION ALL: различия, примеры и проверка производительности
SQL UNION и UNION ALL объяснены с помощью запросов, которые мы выполнили: дубликаты, правила, которым следуют оба, реальная ошибка данных, измеренна…
Читать статью →Разработка ПОУчебное пособие по API JavaScript Fetch: GET, POST, обработка ошибок и тайм-ауты
Учебное пособие по API JavaScript Fetch, протестированное на локальном сервере: GET и POST, почему не выдается ошибка 404, многоразовый помощник, ст…
Читать статью →Разработка ПООбработка файлов Python: чтение, запись, CSV, JSON и pathlib с примерами
Обработка файлов Python объясняется с помощью кода, который мы запускали: открытые режимы, чтение больших файлов, CSV, JSON, pathlib, двоичные файлы…
Читать статью →Разработка ПОКлассы и объекты Python: учебник ООП с реальными примерами
Классы и объекты Python, объясненные с помощью кода, который мы запускали: __init__, методы, атрибуты класса и экземпляра, наследование, свойства, м…
Читать статью →Искусственный интеллектЧто такое большая языковая модель? Как работают LLM, объяснено без шумихи
Что такое большая языковая модель? LLM объясняется простым языком: токены, встраивания, внимание, обучение, температурные и контекстные окна, крошеч…
Читать статью →DevOpsКоманды Linux для начинающих: основное руководство по терминалу с примерами
Команды Linux для начинающих, сгруппированные по задачам: навигация, файлы, просмотр журналов, grep, каналы, разрешения, процессы и архивы, с практи…
Читать статью →Автоматизация тестированияSelenium vs Playwright: ключевые различия, сравнение кода и что выбрать
Сравнение Selenium и Playwright: архитектура, ожидание, поддержка браузера и языка, отладка, одинаковый тест в обоих инструментах, советы по миграци…
Читать статью →Тестирование ПОРазделение эквивалентности: метод проектирования тестов, объясненный на рабочих примерах
Объяснение эквивалентного разделения: как найти действительные и недействительные разделы, рабочие примеры для возрастов, паролей и купонов, проведе…
Читать статью →Тестирование ПОСерьезность и приоритет в тестировании программного обеспечения: различия, матрица и реальные примеры
Объяснение серьезности и приоритета при тестировании программного обеспечения: четкие определения, четырехсторонняя матрица с примерами, шкалы уровн…
Читать статью →Базы данных и SQLУчебное пособие по подзапросам SQL: типы, коррелирующие подзапросы и EXISTS с примерами
Руководство по подзапросам SQL с выполненными нами запросами: скалярные подзапросы, IN, FROM и SELECT, коррелированные подзапросы, EXISTS, ловушка N…
Читать статью →Базы данных и SQLОбъяснение транзакций SQL и ACID: COMMIT, ROLLBACK и уровни изоляции
Транзакции SQL и ACID, объясненные с помощью банковского перевода, который мы выполнили: COMMIT, ROLLBACK, точки сохранения, блокировки, уровни изол…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →