← Все курсы · Курс SQL · Все руководства
NULL в SQL: Почему = NULL ничего не находит и что писать вместо этого
К концу этой страницы вы сможете предсказать, что сделает любой запрос, если он встретит пропущенное значение. Это навык, который отделяет «мой запрос ничего не дал, и я не знаю почему» от двухсекундного исправления. Вы будете знать, почему = NULL соответствует нулю строк, почему один NULL может очистить всю NOT INи как значения NULL незаметно перемещают средние значения, подсчеты, группы и порядок сортировки. Это около двадцати минут.
Вот что на самом деле с этим делать. В следующий раз, когда фильтр вернет меньше строк, чем вы ожидали, сначала задайте один вопрос: есть ли в сравниваемом столбце значения NULL? Бегать WHERE the_column IS NULL и посмотри. Эта одна проверка объясняет большинство загадочных чисел строк еще до начала реальной отладки.
Краткая версия: NULL означает неизвестное, а не ноль и не пустой текст. Сравнение с неизвестными отвечает «неизвестно», и WHERE сохраняет только те строки, которые отвечают «да». Таким образом, каждое сравнение NULL автоматически удаляет строки.
Эти ворота «только да» — единственная идея во всем, что есть на этой странице, поэтому картина понятна.
- Что на самом деле означает NULL и почему = NULL ничего не находит
- Трехзначная логика в повседневных словах
- Ловушка NOT IN: один NULL, ноль строк.
- COALESCE для значений по умолчанию и когда его не следует использовать
- Как значения NULL изменяют ваши значения и средние значения
- Полное до и после
- Пограничные случаи: группы, соединения и порядок сортировки
- Почему это работает
- Использование этого в вашем собственном проекте
- Все на одном экране
SELECT и WHERE новые, начните с Основы SQL и вернись.Вот таблица. Десять билетов. Троим не хватает времени, двум не хватает своего региона, а двое не имеют правопреемника.
| Ticket_id | Регион | минут_до_закрыть | правопреемник |
|---|---|---|---|
| 1 | Восток | 45 | Прия |
| 2 | Восток | NULL | Прия |
| 3 | Запад | 30 | Маркус |
| 4 | Запад | 90 | NULL |
| 5 | NULL | 60 | Маркус |
| 6 | Восток | 15 | Дана |
| 7 | Запад | NULL | NULL |
| 8 | NULL | 120 | Дана |
| 9 | Восток | 75 | Прия |
| 10 | Запад | NULL | Маркус |
1. Что на самом деле означает NULL и почему = NULL ничего не находит
Перед пояснением: два билета в этой таблице не имеют правопреемника. Что вы ожидаете вернуть от этого запроса?
SELECT * FROM tickets WHERE assignee = NULL;
Ноль строк. Не два неназначенных билета. Ноль из десяти, каждый раз, в каждой базе данных. Я запустил его и получил пустой результат, а причина в самом определении NULL.
NULL не означает ноль и не означает пустой текст. NULL означает неизвестно: в этой строке есть слот правопреемника, и никто не сказал, что в нем происходит. Так assignee = NULL спрашивает: «Соответствует ли правопреемник этого билета неизвестному значению?», и единственный честный ответ: «Я не знаю». Даже для билета 4, где правопреемник сам имеет значение NULL, возникает вопрос: «равно ли одно неизвестное другому неизвестному?», и ответ все еще неизвестен. я побежал SELECT NULL = NULL и база данных возвращает NULL, что неверно.
Таким образом, SQL дает вам отдельный глагол для вопроса, который вы на самом деле имели в виду. IS NULL спрашивает: «Это значение отсутствует?», Это вопрос о слоте, а не о значении, поэтому он имеет реальный ответ «да» или «нет».
SELECT * FROM tickets WHERE assignee IS NULL;
| Ticket_id | Регион | минут_до_закрыть | правопреемник |
|---|---|---|---|
| 4 | Запад | 90 | NULL |
| 7 | Запад | NULL | NULL |
Есть два неназначенных билета. IS NOT NULL является зеркалом и возвращает остальные 8 строк. Правило, которое следует соблюдать: = и <> сравнить значения, IS NULL и IS NOT NULL проверьте наличие отсутствующих, они не взаимозаменяемы.
2. Трехзначная логика в повседневных словах
Перед пояснением: на столе 10 билетов и 3 из них принадлежат Прие. Сколько строк занимает WHERE assignee <> 'Priya' возвращаться?
Большинство людей говорят, что семь. В базе данных указано пять. Я пробежал: 5 рядов.
Вот машина, простыми словами. В большинстве случаев на вопрос есть два ответа: да или нет. В SQL есть три сравнения: да, нет и неизвестно, где неизвестное — это то, что вы получаете всякий раз, когда используется NULL. Это называется трехзначной логикой, и имя имеет меньшее значение, чем последствия: WHERE сохраняет только те строки, которые отвечают «да». Строки с ответом «нет» удаляются, как и строки с ответом «неизвестно», автоматически, без ошибок и предупреждений.
Итак, для assignee <> 'Priya': пять билетов отвечают «да» (Маркуса и Даны), три отвечают «нет» (прии), а билеты 4 и 7 отвечают неизвестно, потому что сравнение NULL с «Прией» не дает честного ответа. Неизвестные исчезают. Десять вошли, пять вышли, а два неназначенных билета не находятся ни в стопке «Прия», ни в стопке «Не-прия».
Прежде чем читать дальше, скажите вслух, где находятся билеты 4 и 7, потому что в этом предложении и заключается вся хитрость. Они не были исключены из-за того, что принадлежали Прие. Они были исключены из-за отсутствия ответа. Когда вы действительно имеете в виду «всех, кроме Прии, включая неназначенных», вы должны так и сказать.
SELECT COUNT(*) AS n
FROM tickets
WHERE assignee <> 'Priya' OR assignee IS NULL;
-- n = 7
3. Ловушка NOT IN: один NULL, ноль строк.
Прежде чем объяснить: у вас есть список сотрудников, которые закрывают заявки, и вы хотите, чтобы заявки обрабатывал кто-то, кого нет в списке. В списке три записи: Прия, Маркус и одна NULL из-за неправильного импорта. Как вы думаете, сколько строк вернется?
SELECT * FROM tickets
WHERE assignee NOT IN (SELECT name FROM closers);
Ноль строк. Не «билеты Даны», не «все, кроме Прии и Маркуса». Ноль, и я запустил его, чтобы подтвердить. Это самый неприятный NULL-сюрприз в SQL, поскольку запрос выглядит вполне разумным и возвращает пустой набор без ошибок.
Причина следует из второго шага. NOT IN разворачивается в цепочку сравнений: assignee <> 'Priya' AND assignee <> 'Marcus' AND assignee <> NULL. Последнее сравнение дает неизвестный ответ для каждой строки таблицы. А цепочка операторов AND может ответить «да» только в том случае, если каждая ссылка отвечает «да», поэтому лучшее, что может сделать любая строка, неизвестно. Ни одна строка не отвечает да, WHERE сохраняет только да, а результат пустой. Один NULL в списке отравляет все десять строк.
Два исправления, оба я выполнил. Отфильтруйте NULL из подзапроса, который возвращает 2 билета Дана:
SELECT ticket_id, assignee
FROM tickets
WHERE assignee NOT IN (
SELECT name FROM closers WHERE name IS NOT NULL
);
-- tickets 6 and 8, both Dana
Или используйте NOT EXISTS, который проверяет по одной строке «нет соответствующей строки» и невосприимчив к ловушке. Он вернул 4 строки одних и тех же данных: два билета Даны плюс два неназначенных, поскольку неназначенный билет действительно не имеет совпадений в списке. Эти два исправления расходятся во мнениях относительно билетов 4 и 7, и ни одно из них не является ошибочным. Они отвечают на разные вопросы, поэтому развилка такова: должны ли неназначенные билеты считаться «нет в списке»? Если да, NOT EXISTS говорит то, что вы имеете в виду. Если нет, экранированный NOT IN делает. Решающее значение имеет предложение, которое вы бы сказали заинтересованной стороне, и стоит вписать это предложение в запрос в качестве комментария.
4. COALESCE для значений по умолчанию и когда его не следует использовать
Перед пояснением: отчет о вашей заявке поступает к менеджеру, который постоянно спрашивает, что означают пустые ячейки «правопреемник». Что бы вы хотели, чтобы эти клетки говорили вместо этого?
COALESCE это инструмент. Она принимает список значений и возвращает первое из них, отличное от NULL, что делает ее функцией заполнения пробелов: COALESCE(assignee, 'Unassigned') означает «правопреемник» или слово «Неназначенный», если его нет.
SELECT ticket_id,
COALESCE(assignee, 'Unassigned') AS assignee_display
FROM tickets
ORDER BY ticket_id;
Я запустил его: на билетах 4 и 7 теперь написано «Не назначено», а имена остальных восьми показаны без изменений. Это честное использование COALESCE: маркировка отсутствий, чтобы читатель мог их увидеть.
Нечестное использование скрывает это. COALESCE(minutes_to_close, 0) заставляет NULL превращаться в нули, а время закрытия в нулевую минуту — это утверждение, а не метка: оно говорит, что билет закрыт мгновенно, хотя на самом деле никто его не записал. Три из десяти билетов здесь не имеют близкого времени. Это 30%-ная дыра в столбце, и дыра такого размера — это открытие, о котором ваш читатель заслуживает услышать, а не проблема форматирования, которую нужно сгладить. Когда вы заполняете значение для отображения, укажите это в отчете, а если пропущенное значение велико, сообщите об этом как в отдельной строке. Документирование ограничений данных рассказывает о том, как это написать, не скрывая анализ.
5. Как значения NULL изменяют ваши значения и средние значения
Перед объяснением: minutes_to_close имеет семь действительных значений и три NULL. Когда ты принимаешь AVG(minutes_to_close)на какое число делит база данных: на десять или на семь?
Семь. Агрегатные функции полностью пропускают NULL: SUM, AVG, MIN, MAX, и COUNT(column) все действуют только на те строки, где существует значение. Я пробежал числа по обработанной таблице.
SELECT COUNT(*) AS all_rows,
COUNT(minutes_to_close) AS with_minutes,
AVG(minutes_to_close) AS avg_minutes
FROM tickets;
| все_строки | с_минутами | avg_минуты |
|---|---|---|
| 10 | 7 | 62.1 |
Среднее значение составляет 435, разделенное на 7, что составляет 62,1 минуты. Принудительно обнулить NULL с помощью COALESCE и в том же столбце среднее значение составляет 43,5, потому что теперь это 435, разделенное на 10. Это 30-процентное колебание в цифре заголовка из-за одного решения о недостающих данных, и ни одно из чисел не является автоматически правильным. 62,1 — это среднее значение билетов, время закрытия которых было зафиксировано. 43.5 притворяется, что незаписано, значит мгновенно. В честном отчете указано первое число и дыра: «В среднем 62,1 минуты по 7 из 10 билетов с зафиксированным временем закрытия».
COUNT(*) против COUNT(column) это тот же пропуск в форме счета. COUNT(*) считает строки, здесь 10. COUNT(minutes_to_close) подсчитывает значения, отличные от NULL, здесь 7, и COUNT(assignee) дает 8. Разрыв между двумя значениями — это количество пропущенных данных, что делает эту пару самой быстрой проверкой полноты в SQL. Вся семья COUNT, включая DISTINCT, находится в СЧЕТ в SQL.
Теперь картинка работает COUNT(*) рядом с COUNT(the_column) в самом важном числовом столбце ваших собственных данных. Знаете ли вы прямо сейчас, какой будет разрыв? Большинство людей узнают об этом позже, чем им хотелось бы.
6. Полное до и после
Та же таблица, тот же вопрос: как быстро каждый регион закрывает заявки?
До
SELECT region, AVG(minutes_to_close) AS avg_minutes
FROM tickets
GROUP BY region;
Три тихие проблемы, никаких ошибок. Среднее значение молча пропускает три незарегистрированных момента закрытия, поэтому никто не может увидеть, какой объем данных стоит за каждым числом. Два билета без указания региона образуют отдельную пустую группу без каких-либо объяснений. И читатель не имеет возможности узнать об этом из вывода.
после
-- ============================================================
-- STEP 4: How fast does each region close tickets?
-- WHY: minutes_to_close is missing on 3 of 10 tickets, and
-- region is missing on 2. Averages skip the NULLs, so
-- each average is shown WITH how many tickets back it.
-- NOTE: 'No region' is real missing data, reported as its
-- own line rather than dropped or blank.
-- ============================================================
--SELECT the region (labelled when missing), tickets in the
-- group, how many have a recorded close time, and their avg
SELECT COALESCE(region, 'No region') AS region_display,
COUNT(*) AS tickets,
COUNT(minutes_to_close) AS with_close_time,
ROUND(AVG(minutes_to_close), 1) AS avg_minutes
FROM tickets
GROUP BY region;
Пустая группа теперь имеет имя, каждое среднее значение находится рядом с количеством значений, которые его создали, а отсутствие отображается вместо окружающего. Формат комментариев тот же, что и у Как комментировать SQL, чтобы он обучал. Чтобы передать эту форму пальцам, введите запрос самостоятельно в SQL-практикум, а не вставлять его.
7. Пограничные случаи: группы, соединения и порядок сортировки
Перед объяснением: NULL никогда не равен NULL, однако два билета без региона только что попали в одну группу вместе. Как и то, и другое может быть правдой?
Потому что разные части SQL намеренно обрабатывают NULL по-разному, и в этих швах живут сюрпризы. Четыре, чтобы знать.
GROUP BY помещает все NULL в одну группу. При группировке задается вопрос «какие строки принадлежат друг другу?», а стандартный ответ отвечает, что все пропущенные значения принадлежат друг другу, даже если среди них нет двух одинаковых. В рабочей таблице группировка по регионам возвращает Запад 4, Восток 4 и группу NULL, равную 2, что я и проверил, чтобы подтвердить. Эта пустая на вид строка не является сбоем. Это ваш счетчик недостающих регионов, и его стоит читать каждый раз.
LEFT JOIN создает NULL. LEFT JOIN сохраняет каждую строку из левой таблицы, а когда строка не находит совпадений в правой, правые столбцы получают значение NULL. Присоединив билеты к двухстрочному поиску регионов, в котором указан только Восток, я запустил его: четыре билета Востока получают своего менеджера, а остальные шесть строк показывают NULL, охватывая как настоящие билеты Запада без записи поиска, так и билеты, регион которых уже отсутствовал. Таким образом, NULL после соединения означает «нет совпадения или отсутствующий ключ», и их разделение требует IS NULL проверьте ключ соединения. SQL-СОЕДИНЕНИЯ гуляет всей семьей.
ORDER BY должен где-то помещать NULL, а базы данных с этим не согласны. Сортировка обработанной таблицы по minutes_to_close при возрастании в SQLite сначала помещаются три строки NULL, а затем 15; спуск ставит их на последнее место. SQLite и SQL Server рассматривают NULL как наименьшие, PostgreSQL и Оракул рассматривают их как самые большие, а PostgreSQL и Оракул принимают явные значения. NULLS FIRST или NULLS LAST сказать то, что вы имеете в виду. Переносимая привычка: когда отсортированный отчет имеет значение, укажите его место либо с помощью этого пункта, либо путем сортировки по the_column IS NULL первый.
Пустой текст не имеет значения NULL, за исключением случаев, когда он есть. Пустая строка — это известное значение: кто-то сказал «ничего», а не никто ничего не сказал. В большинстве баз данных фильтры и счетчики обрабатывают эти два значения по-разному, поэтому столбец может выглядеть пустым, пока IS NULL ничего не находит. Оракул — известное исключение, сохраняющее пустые строки как NULL. Если пробелы ведут себя странно, проверьте оба: WHERE col IS NULL OR col = ''.
Почему это работает
Все это не пустяки для запоминания по пунктам. Все это вытекает из одного проектного решения: реляционная база данных должна представлять «неизвестное значение», не придумывая поддельное значение, и каждый оператор затем должен честно ответить, когда он встретит такое значение. Реляционная модель Кодда представила именно эту идею: отсутствие информации является первоклассным состоянием, а не магическим числом вроде 0 или 999 (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). Как только «неизвестное» существует, применяется трехзначная логика: сравнение с неизвестным не может честно ответить «да» или «нет», поэтому должен существовать третий ответ, и отсюда следует все поведение на этой странице. = NULL неизвестен, цепочка операторов AND с одним неизвестным звеном в лучшем случае неизвестна, а агрегат по неизвестным может использовать только те значения, которые он действительно имеет.
Страница также продолжает просить вас подтвердить количество строк, прежде чем показывать их, и это намеренно. Создание ответа до того, как вы увидите правильный, надежно улучшает удержание, даже если предположение неверно (Бисра, Лю, Несбит, Салими и Винн, 2018, Обзор педагогической психологии, 30(3), 703–725) и получение битов, повторное чтение для их сохранения (Краснее & выбирать карпа, 2006, Психологическая наука, 17(3), 249–255). Угадать семь и получить пять, вот почему <> отбрасывание неизвестного все еще будет с вами в следующем месяце.
Использование этого в вашем собственном проекте
Аудит каждого вашего запроса на обработку NULL за один проход — утомительное занятие, и никто его не завершает. Вместо этого сделайте это по порядку.
- Профиль одной таблицы: запустить
COUNT(*)рядом сCOUNT(col)для каждого важного столбца. Пробелы — это ваша карта недостающих данных, и в большинстве таблиц есть только два или три столбца, о которых стоит беспокоиться. - Найдите в сохраненных запросах
NOT INс подзапросом. Каждый из них либо уже проверенIS NOT NULLили это сюрприз с нулевыми строками, ожидающий прибытия первого NULL. Исправьте или преобразуйте вNOT EXISTS. - Найдите каждого
<>в предложении WHERE и спросите, принадлежат ли в ответе строки NULL. Если да, добавьтеOR col IS NULL. Если нет, оставьте это и идите дальше. - Укажите количество значений рядом с каждым публикуемым средним значением. «62,1 по шкале 7 из 10» — это утверждение, отличное от «62,1», и читатель заслуживает знать, какое утверждение вы делаете.
- Метка отображает NULL с
COALESCEи заполненные журналом. Значение по умолчанию, которое изменяет арифметику, относится к примечанию об ограничениях, а не только к запросу.
Если у вас под рукой есть бумага и есть пять минут, один дополнительный рисунок занимает свое место. Нарисуйте шесть рядов, ведущих к воротам, отметьте два «да», два «нет» и два вопросительными знаками и нарисуйте только ряды «да», выходящие с дальней стороны. Перерисовка этого по памяти на следующей неделе станет честной проверкой того, принадлежат ли вам теперь ворота «только да».
Все на одном экране
Это лист поиска. Закройте правую колонку, проработайте левую и произнесите каждый ответ вслух, прежде чем проверять его.
| концепция | Что он делает |
|---|---|
| NULL | Неизвестный. Не ноль, не пустой текст. Место, которое никто не заполнил. |
= NULL | Ответы неизвестны для каждой строки, поэтому она ничему не соответствует. Всегда. |
IS NULL / IS NOT NULL | Настоящий тест на пропущенные значения. Спрашивает о слоте, а не о значении. |
| Трехзначная логика | Сравнения отвечают да, нет или неизвестно. NULL всегда выдает неизвестное. |
WHERE | Сохраняет только строки «да». Неизвестные строки удаляются автоматически, так же, как и нет. |
<> 'x' | Удаляет также NULL-строки. Добавлять OR col IS NULL если они принадлежат. |
NOT IN с NULL в списке | Возвращает ноль строк, без ошибок. Просмотрите подзапрос или используйте NOT EXISTS. |
NOT EXISTS | Невосприимчив к ловушке и считает NULL-строки несовпадающими. Решите, это ли вы имеете в виду. |
COALESCE(col, default) | Первое значение, отличное от NULL. Отметьте его отсутствие; не переписывайте молча цифры. |
COUNT(*) против COUNT(col) | Строки и значения, отличные от NULL. Разрыв — это количество недостающих данных. |
| Агрегаты | SUM, AVG, MIN, MAX пропустить NULL. AVG делится на сохраненные значения. |
NULL = NULL | Неизвестно, неправда. Ни один NULL никогда не равен другому. |
GROUP BY | По-прежнему помещает все NULL в одну группу. Эта группа и есть ваш недостающий счет. |
| LEFT JOIN | Несовпадающие строки поступают с NULL в правых столбцах. |
ORDER BY | NULL сортируются первыми или последними в зависимости от базы данных. Сказать NULLS FIRST/LAST где поддерживается. |
| Пустая строка | Известное значение, почти везде отличное от NULL. Оракул сохраняет его как NULL. |
COUNT(*) рядом с COUNT(the_column) прежде чем доверять какому-либо фильтру, объединению или среднему значению в этом столбце. У NULL-ошибок на этой странице есть одна общая черта: нет сообщения об ошибке, просто неправильное количество строк, похожее на результат. Если запрос ведет себя не так, как описано на этой странице, существует общая ошибка. шлейф диагностики на предмет застревания.Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Моей самой худшей NULL-ошибкой был NOT IN, который возвращал ноль строк в течение недели, а в отчете, который он подавал, просто говорилось: «Исключения не найдены», чему все были рады поверить. Как долго NULL тихо лгал вам и что в конце концов его выдало?
Ссылки
- Кодд, EF (1970). Реляционная модель данных для крупных общих банков данных. Коммуникации ACM, 13(6), 377–387.
- Бисра К., Лю К., Несбит Дж. К., Салими Ф. и Винн П. Х. (2018). Побуждение к самообъяснению: метаанализ. Обзор педагогической психологии, 30(3), 703–725.
- Рёдигер, Х.Л., и Карпике, Дж.Д. (2006). Улучшенное обучение с помощью тестов: прохождение тестов на память улучшает долгосрочное запоминание. Психологическая наука, 17(3), 249–255.
SQL для аналитиков составляет 458 страниц, NULL разбирается в каждом конкретном случае, потому что это отсутствие значения, а не значение.
SQL для аналитиков, 19 $ →тот Курс SQL запускает SQLite в браузере, поэтому вы можете заполнять таблицу значениями NULL, запускать запросы-ловушки и наблюдать за изменением количества строк по мере их исправления. Рядом с ним лежат рабочие примеры, карточки и пробный экзамен. Ничего устанавливать, учетная запись не требуется.
Откройте набор SQL →