← Все курсы · Курс SQL · Все руководства

NULL в SQL: Почему = NULL ничего не находит и что писать вместо этого

NULL означает «неизвестно», а «неизвестное» прерывает все вопросы, требующие ответа «да» или «нет» · Написано для прослушивания · Часть Analyst Prep Kit

К , аналитик данных · Опубликовано

К концу этой страницы вы сможете предсказать, что сделает любой запрос, если он встретит пропущенное значение. Это навык, который отделяет «мой запрос ничего не дал, и я не знаю почему» от двухсекундного исправления. Вы будете знать, почему = NULL соответствует нулю строк, почему один NULL может очистить всю NOT INи как значения NULL незаметно перемещают средние значения, подсчеты, группы и порядок сортировки. Это около двадцати минут.

Вот что на самом деле с этим делать. В следующий раз, когда фильтр вернет меньше строк, чем вы ожидали, сначала задайте один вопрос: есть ли в сравниваемом столбце значения NULL? Бегать WHERE the_column IS NULL и посмотри. Эта одна проверка объясняет большинство загадочных чисел строк еще до начала реальной отладки.

Краткая версия: NULL означает неизвестное, а не ноль и не пустой текст. Сравнение с неизвестными отвечает «неизвестно», и WHERE сохраняет только те строки, которые отвечают «да». Таким образом, каждое сравнение NULL автоматически удаляет строки.

Эти ворота «только да» — единственная идея во всем, что есть на этой странице, поэтому картина понятна.

WHERE keeps only yes A left-to-right picture of six rows approaching a gate labelled WHERE. Each row carries the answer its comparison produced: two rows answer yes, two rows answer no, and two rows carry a question mark meaning unknown, because their compared value was NULL. Beyond the gate there are three paths. The yes path continues through to the result, holding two rows. The no path is stopped at the gate. The unknown path is drawn separately in a warning color, and it is also stopped at the gate, landing in the same discard area as the no rows. The picture shows that a comparison in SQL can answer yes, no, or unknown, and that the WHERE gate lets only yes rows pass, so unknown rows disappear from the result exactly as if they had answered no. rows WHERE result ✓ ✓ ✕ ✕ ? ? ✕ ✕ ✕ ✕ no unknown
Сравнение может дать ответ «да», «нет» или «неизвестно». WHERE сохраняет только строки «да», поэтому строка, сравнение которой соответствует NULL, отбрасывается точно так же, как если бы она ответила «нет».
Что вы узнаете
  1. Что на самом деле означает NULL и почему = NULL ничего не находит
  2. Трехзначная логика в повседневных словах
  3. Ловушка NOT IN: один NULL, ноль строк.
  4. COALESCE для значений по умолчанию и когда его не следует использовать
  5. Как значения NULL изменяют ваши значения и средние значения
  6. Полное до и после
  7. Пограничные случаи: группы, соединения и порядок сортировки
  8. Почему это работает
  9. Использование этого в вашем собственном проекте
  10. Все на одном экране
Проработанный пример реальный. Каждое число на этой странице взято из 10-строчной таблицы заявок на поддержку, в которой намеренно засеяны NULL-значения, и каждый запрос выполнялся к нему в SQLite, прежде чем его выходные данные были вставлены сюда. Таблица достаточно маленькая, чтобы ее можно было проверить на глаз. Если SELECT и WHERE новые, начните с Основы SQL и вернись.

Вот таблица. Десять билетов. Троим не хватает времени, двум не хватает своего региона, а двое не имеют правопреемника.

Ticket_idРегионминут_до_закрытьправопреемник
1Восток45Прия
2ВостокNULLПрия
3Запад30Маркус
4Запад90NULL
5NULL60Маркус
6Восток15Дана
7ЗападNULLNULL
8NULL120Дана
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Запад90NULL
7ЗападNULLNULL

Есть два неназначенных билета. 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_минуты
10762.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 за один проход — утомительное занятие, и никто его не завершает. Вместо этого сделайте это по порядку.

  1. Профиль одной таблицы: запустить COUNT(*) рядом с COUNT(col) для каждого важного столбца. Пробелы — это ваша карта недостающих данных, и в большинстве таблиц есть только два или три столбца, о которых стоит беспокоиться.
  2. Найдите в сохраненных запросах NOT IN с подзапросом. Каждый из них либо уже проверен IS NOT NULL или это сюрприз с нулевыми строками, ожидающий прибытия первого NULL. Исправьте или преобразуйте в NOT EXISTS.
  3. Найдите каждого <> в предложении WHERE и спросите, принадлежат ли в ответе строки NULL. Если да, добавьте OR col IS NULL. Если нет, оставьте это и идите дальше.
  4. Укажите количество значений рядом с каждым публикуемым средним значением. «62,1 по шкале 7 из 10» — это утверждение, отличное от «62,1», и читатель заслуживает знать, какое утверждение вы делаете.
  5. Метка отображает NULL с COALESCEи заполненные журналом. Значение по умолчанию, которое изменяет арифметику, относится к примечанию об ограничениях, а не только к запросу.

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

Подробнее об этом и еще что-нибудь подобное. Все инструкции находятся в одном месте на индекс направляющих: SQL, Tableau, миграция данных и связанные с ними рабочие привычки.

Все на одном экране

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

концепцияЧто он делает
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 BYNULL сортируются первыми или последними в зависимости от базы данных. Сказать NULLS FIRST/LAST где поддерживается.
Пустая строкаИзвестное значение, почти везде отличное от NULL. Оракул сохраняет его как NULL.
Единственная привычка, которую следует сохранить. Если вы больше ничего не берете с этой страницы, запустите COUNT(*) рядом с COUNT(the_column) прежде чем доверять какому-либо фильтру, объединению или среднему значению в этом столбце. У NULL-ошибок на этой странице есть одна общая черта: нет сообщения об ошибке, просто неправильное количество строк, похожее на результат. Если запрос ведет себя не так, как описано на этой странице, существует общая ошибка. шлейф диагностики на предмет застревания.

Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Моей самой худшей NULL-ошибкой был NOT IN, который возвращал ноль строк в течение недели, а в отчете, который он подавал, просто говорилось: «Исключения не найдены», чему все были рады поверить. Как долго NULL тихо лгал вам и что в конце концов его выдало?

Ссылки

Запрос почти работает, и исчезли строки, в этом столбце ничего не было.

SQL для аналитиков составляет 458 страниц, NULL разбирается в каждом конкретном случае, потому что это отсутствие значения, а не значение.

SQL для аналитиков, 19 $ →
Попрактикуйтесь в обработке NULL, используя живые запросы.

тот Курс SQL запускает SQLite в браузере, поэтому вы можете заполнять таблицу значениями NULL, запускать запросы-ловушки и наблюдать за изменением количества строк по мере их исправления. Рядом с ним лежат рабочие примеры, карточки и пробный экзамен. Ничего устанавливать, учетная запись не требуется.

Откройте набор SQL →