SQL

БЛОК 2. Чтение данных — 7. Логические операторы и NULL

📚 22 вопросовПройти тест →
Лекция

БЛОК 2. Чтение данных — 7. Логические операторы и NULL

SQL

7. Логические операторы и NULL

🧭 Введение: почему логика фильтров ломается именно на NULL

Почти любой рабочий SQL-запрос быстро становится сложнее: появляются AND, OR, NOT, списки статусов, диапазоны дат и шаблонный поиск.
И в этот момент в игру входит NULL, из-за которого «очевидные» фильтры начинают вести себя неожиданно.
В SQL логика не двухзначная, а трёхзначная: TRUE, FALSE, UNKNOWN.
Именно UNKNOWN чаще всего объясняет, почему строка не попала в результат, хотя «по ощущениям должна была».
💡 Совет: Если запрос вернул не тот набор строк, первым делом проверяйте наличие NULL в полях фильтра.
Вывод: Понимание AND/OR/NOT вместе с NULL и UNKNOWN — это база корректной фильтрации в SQL.

⚠️ Проблема -> решение

Без системного подхода фильтры пишут «на интуиции»: смешивают AND/OR без скобок, используют = NULL, применяют NOT IN к данным с NULL.
Синтаксис при этом часто валиден, но бизнес-результат ошибочный.
Решение — строить фильтр по шагам:
  1. сначала базовый предикат,
  2. затем логические связки со скобками,
  3. отдельная обработка NULL через IS NULL / IS NOT NULL,
  4. проверка поведения на тестовых кейсах с NULL.
🟢 Если совсем просто: Пока не продумали NULL, фильтр не считается завершённым.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, что произойдёт для строки, где одно из условий равно NULL.

🛠️ Чем помогает и как работает

Точная логика фильтра снижает риск «тихих» багов в отчётах, API и админке.
Это особенно важно в проде, где одна ошибка в WHERE меняет бизнес-решения на основе данных.
🟢 Если совсем просто: Вы учитесь задавать БД не просто вопрос, а корректный логический вопрос.
🎯 Как понять, что этап прошёл успешно: Запрос стабильно даёт ожидаемый результат даже при неполных данных.
Чем помогает:
  • делает фильтры предсказуемыми;
  • уменьшает баги из-за NULL;
  • ускоряет отладку сложных условий;
  • улучшает качество code review.
Как это работает:
  • Шаг 1: описываем условие человеческим языком.
  • Шаг 2: переводим его в простые предикаты.
  • Шаг 3: объединяем предикаты через AND/OR/NOT со скобками.
  • Шаг 4: отдельно проверяем ветки, где поля могут быть NULL.
  • Шаг 5: используем IN/BETWEEN/LIKE только там, где это уместно.
  • Шаг 6: проверяем запрос на контрольных кейсах с NULL.
Вывод: Корректная фильтрация в SQL = логические операторы + явная стратегия для NULL.

📚 Ключевые термины (простыми словами)

Перед практикой важно синхронизировать словарь логики SQL.
🟢 Если совсем просто: Эти термины нужны, чтобы одинаково понимать поведение WHERE.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить любой из терминов на примере конкретного запроса.
  • AND (логическое И) — все условия должны быть истинны.
  • OR (логическое ИЛИ) — достаточно истинности хотя бы одного условия.
  • NOT (логическое НЕ) — инвертирует условие.
  • IN (в списке) — проверка значения по набору вариантов.
  • BETWEEN (в диапазоне) — проверка в диапазоне с включением границ.
  • LIKE (по шаблону) — поиск по строковому шаблону.
  • NULL (отсутствующее значение) — значение неизвестно/не задано.
  • IS NULL / IS NOT NULL — корректные проверки на NULL.
  • Three-valued logic (трёхзначная логика)TRUE, FALSE, UNKNOWN.
  • Predicate (предикат) — условие, возвращающее логический результат.
Вывод: Без понимания NULL и UNKNOWN даже простой WHERE может вести себя неожиданно.

🔗 1. AND / OR / NOT: каркас логики фильтра

Логические операторы — это базовый язык условий в SQL.
Проблема обычно начинается при смешивании AND и OR без явных скобок.
🟢 Если совсем просто: AND сужает выборку, OR расширяет, NOT исключает.
🎯 Как понять, что этап прошёл успешно: Вы точно понимаете, какие строки попадут в результат до запуска запроса.
Назначение: Комбинировать несколько условий в одно бизнес-правило.
Простыми словами: Вы описываете, какие критерии обязательны, какие альтернативны, а какие нужно исключить.
Для новичка: Если в условии одновременно есть AND и OR, ставьте скобки сразу, даже если «и так понятно».
Аналогия: Это как проход в зону: «сотрудник отдела A или B, и только с активным пропуском».
Пример:
SELECT  id,  status,  priority,  is_escalatedFROM ticketsWHERE (status = 'open' OR status = 'in_progress')  AND priority >= 3  AND is_escalated IS NOT TRUE;
💡 Важно: В этом примере мы осознанно используем is_escalated IS NOT TRUE вместо NOT is_escalated, потому что NOT на nullable BOOLEAN может дать UNKNOWN.
AND NOT is_escalatedAND is_escalated IS NOT TRUE
🔎 Как это происходит на практике:
  • Контекст: support формирует рабочую очередь «активных и приоритетных» обращений.
  • Действия: статусы объединяют через OR, обязательные критерии задают через AND.
  • Результат: в выборке только нужные заявки без ручной фильтрации.
Характеристики:
  • AND требует истинности всех условий;
  • OR требует истинности хотя бы одного условия;
  • NOT меняет логический результат условия;
  • для nullable BOOLEAN безопасный шаблон: flag IS TRUE / flag IS NOT TRUE (или COALESCE(flag, false) = false по бизнес-правилу);
  • скобки снимают неоднозначность приоритета.
Когда использовать: Всегда, когда фильтр состоит более чем из одного критерия.
Вывод: Сложное условие без скобок — потенциальный баг, даже если SQL выполняется.

🧩 2. IN / BETWEEN / LIKE: компактные условия

Эти операторы сокращают длинные цепочки однотипных сравнений и делают запросы читаемее.
Но они тоже подчиняются правилам NULL и трёхзначной логики.
🟢 Если совсем просто: IN — список, BETWEEN — диапазон, LIKE — текстовый шаблон.
🎯 Как понять, что этап прошёл успешно: Ваш запрос короче, но не теряет точность логики.
Назначение: Записывать типовые фильтры компактно и поддерживаемо.
Простыми словами: Вы заменяете длинные повторяющиеся условия на короткие конструкции.
Для новичка: Если у вас много OR по одному полю, почти всегда стоит перейти на IN.
Аналогия: Это как фильтры в приложении: «статусы из списка», «цена от и до», «email заканчивается на домен».
Пример:
SELECT  id,  status,  total_amount,  customer_emailFROM ordersWHERE status IN ('paid', 'shipped', 'delivered')  AND total_amount BETWEEN 1000 AND 5000  AND customer_email LIKE '%@company.com';
🔎 Как это происходит на практике:
  • Контекст: аналитика строит сегмент корпоративных заказов в заданном диапазоне.
  • Действия: однотипные условия сжимают в IN, диапазон задают через BETWEEN.
  • Результат: запрос короче и быстрее читается в ревью.
Характеристики:
  • IN (...) удобен для конечного списка значений;
  • BETWEEN a AND b включает обе границы;
  • field IN (...) при field = NULL даёт UNKNOWN, поэтому строка не пройдёт WHERE;
  • field NOT IN (...) может дать UNKNOWN даже для не-NULL поля, если в списке/подзапросе есть NULL;
  • LIKE использует % и _ как шаблонные символы.
Когда использовать: Когда условие естественно выражается списком, диапазоном или шаблоном строки.
Вывод: IN/BETWEEN/LIKE упрощают SQL, но не отменяют необходимость учитывать NULL.

🕳️ 3. IS NULL / IS NOT NULL: обязательный механизм

NULL нельзя корректно проверить через = или <>.
Это одна из самых частых ошибок новичка в WHERE.
🟢 Если совсем просто: Для NULL есть отдельные операторы: только IS NULL и IS NOT NULL.
🎯 Как понять, что этап прошёл успешно: Вы никогда не пишете = NULL в рабочих запросах.
Назначение: Корректно отбирать строки с отсутствующим или присутствующим значением.
Простыми словами: NULL — это не «пустая строка» и не «ноль», это неизвестное значение.
Для новичка: Если поле может быть не заполнено, сразу добавляйте отдельную проверку на NULL.
Аналогия: Это как анкета без ответа в поле: это не «ответ нет», а «ответ не указан».
Пример:
SELECT  id,  phone,  emailFROM customersWHERE phone IS NULL  OR email IS NULL;
🔎 Как это происходит на практике:
  • Контекст: CRM-команда ищет записи, где не хватает контактов.
  • Действия: применяют IS NULL к нужным полям вместо = NULL.
  • Результат: список реально неполных профилей, а не «пустая выдача».
Характеристики:
  • IS NULL проверяет отсутствие значения;
  • IS NOT NULL проверяет наличие значения;
  • = NULL и <> NULL не работают как ожидается.
Когда использовать: В любой таблице, где поле допускает NULL.
Вывод: Работа с NULL всегда требует отдельной явной ветки в фильтре.

🎛️ 4. Трёхзначная логика SQL: TRUE / FALSE / UNKNOWN

В SQL результат предиката может быть не только истинным или ложным, но и UNKNOWN.
UNKNOWN появляется, когда в условии участвует NULL.
🟢 Если совсем просто: Если SQL «не знает», истинно ли условие, он получает UNKNOWN.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, почему строка с NULL не прошла WHERE.
Назначение: Понять, как реально интерпретируются условия с NULL.
Простыми словами: NULL ломает привычную двоичную логику «да/нет».
Для новичка: Запомните: в WHERE остаются только строки, где результат условия = TRUE.
Аналогия: Если нет данных для решения, это не «нет», это «неизвестно».
Пример:
SELECT  id,  assignee_id,  statusFROM tasksWHERE assignee_id <> 10;
🔎 Как это происходит на практике:
  • Контекст: разработчик хочет «все задачи, кроме сотрудника 10».
  • Действия: пишет assignee_id <> 10, но забывает, что часть задач без исполнителя (NULL).
  • Результат: задачи с NULL исчезают из выборки, хотя бизнес мог ожидать обратное.
Характеристики:
  • TRUE проходит через WHERE;
  • FALSE и UNKNOWN отбрасываются;
  • NULL в сравнении часто приводит к UNKNOWN.
Когда использовать: Всегда как ментальную модель при проектировании условий с nullable-полями.
Вывод: Трёхзначная логика объясняет большинство «странных» результатов фильтрации.

🧨 5. Классические ловушки с NULL

Есть несколько типовых конструкций, которые чаще всего ломают выборку в проде.
Их нужно знать как «красные флаги».
🟢 Если совсем просто: Главные ловушки: = NULL, NOT IN с NULL, и забытый NULL в <>.
🎯 Как понять, что этап прошёл успешно: Вы заранее проверяете, есть ли NULL в колонках, участвующих в NOT IN и <>.
Назначение: Показать самые частые ошибки, которые дают валидный SQL, но неправильный результат.
Простыми словами: Некоторые конструкции формально корректны, но логически опасны при NULL.
Для новичка: Если видите NOT IN, сразу задайте вопрос: «а нет ли NULL в подзапросе/списке?».
Аналогия: Это как формально заполненный документ с критически пустым полем.
Пример:
SELECT  id,  role_idFROM usersWHERE role_id NOT IN (1, 2, NULL);
Безопасный шаблон для подзапроса (NOT EXISTS):
SELECT  u.id,  u.emailFROM users AS uWHERE NOT EXISTS (  SELECT 1  FROM banned_users AS b  WHERE b.user_id = u.id);
🔎 Как это происходит на практике:
  • Контекст: команда исключает системные роли из выборки пользователей.
  • Действия: случайно добавляет NULL в список NOT IN.
  • Результат: выдача оказывается пустой или сильно непредсказуемой.
Характеристики:
  • NOT IN чувствителен к NULL в списке/подзапросе;
  • <> не включает NULL, если не добавить явную ветку;
  • если бизнес-правило говорит «NULL тоже включать», добавляем ... OR field IS NULL;
  • если NULL включать нельзя, явно фиксируем это через ... AND field IS NOT NULL;
  • в подзапросах NULL часто появляется случайно (например, из-за LEFT JOIN, nullable FK или «грязных» данных), поэтому NOT EXISTS обычно надёжнее NOT IN.
Когда использовать: Как чек-лист при ревью любого сложного WHERE.
Вывод: Большинство NULL-багов можно предотвратить, если проверять ловушки заранее.

🧪 6. PostgreSQL: IS DISTINCT FROM / IS NOT DISTINCT FROM

Для PostgreSQL есть практичные операторы, которые делают сравнение с NULL предсказуемым.
Это особенно полезно, когда нужно поведение «как равно/не равно», но без ловушек трёхзначной логики.
🟢 Если совсем просто: IS DISTINCT FROM — это «безопасное не равно», а IS NOT DISTINCT FROM — «безопасное равно».
🎯 Как понять, что этап прошёл успешно: Вы можете заменить часть условий <> ... OR ... IS NULL на более короткую и понятную запись.
Назначение: Сравнивать значения с корректной обработкой NULL.
Простыми словами: Эти операторы считают NULL полноценным участником сравнения.
Для новичка: Если хотите «всё, кроме 10, включая NULL», удобно писать assignee_id IS DISTINCT FROM 10.
Аналогия: Это как строгий компаратор, который умеет работать даже когда одно из значений «не указано».
Пример:
SELECT  id,  assignee_idFROM tasksWHERE assignee_id IS DISTINCT FROM 10;
🔎 Как это происходит на практике:
  • Контекст: нужно исключить конкретного исполнителя, но не потерять неназначенные задачи.
  • Действия: вместо assignee_id <> 10 OR assignee_id IS NULL используют IS DISTINCT FROM.
  • Результат: короче код, меньше риск логической ошибки.
Характеристики:
  • a IS DISTINCT FROM b работает как «не равно», но учитывает NULL;
  • a IS NOT DISTINCT FROM b работает как «равно», включая случай NULL = NULL;
  • конструкция снижает количество ручных OR ... IS NULL.
Когда использовать: В PostgreSQL-запросах, где обычные = / <> дают слишком хрупкую логику при nullable-полях.
Вывод: IS DISTINCT FROM и IS NOT DISTINCT FROM — надёжные инструменты для сравнения с NULL в PostgreSQL.

⚖️ Сравнение операторов и поведения с NULL

КонструкцияЧто делаетПоведение при NULL
field = valueточное совпадениеесли field = NULL -> UNKNOWN
field <> valueне равно значениюNULL не пройдёт без отдельной ветки
field IS NULLпроверка отсутствиякорректный способ работы с NULL
field IS NOT NULLпроверка наличиякорректный способ работы с NULL
field IN (...)совпадение со спискомесли field = NULL, результат UNKNOWN, строка не пройдёт WHERE
field NOT IN (...)исключение спискаесли в списке/подзапросе есть NULL, результат может стать UNKNOWN
field LIKE patternшаблон строкидля NULL результат UNKNOWN
Вывод: Для NULL нужны отдельные правила, обычные сравнения недостаточны.

📌 Must-know факты

  • В SQL логика трёхзначная: TRUE, FALSE, UNKNOWN.
  • В WHERE остаются только строки с результатом TRUE.
  • = NULL и <> NULL не используются в рабочих фильтрах.
  • Для nullable-полей почти всегда нужна явная NULL-ветка.
  • NOT IN и NULL — одна из самых частых причин пустых/ломаных выборок.
  • Скобки в смешанных условиях AND/OR обязательны для читаемости и точности.

🧨 Частые мифы

Миф: NULL — это просто пустое значение, с ним можно работать как с обычным.
Как правильно: NULL — отдельная семантика «неизвестно», его проверяют через IS NULL / IS NOT NULL.
📎 Почему это важно: Иначе фильтр будет логически неверным даже при валидном синтаксисе.
Миф: field <> 10 включает все значения, кроме 10, включая NULL.
Как правильно: NULL не проходит такое условие автоматически; при необходимости добавляют OR field IS NULL.
📎 Почему это важно: Без явной ветки можно потерять часть записей и исказить отчёт.
Миф: NOT IN всегда безопаснее и проще.
Как правильно: С NULL в списке/подзапросе NOT IN может работать неожиданно, нужно проверять входные данные.
📎 Почему это важно: Это классический источник «почему выборка внезапно пустая».
Миф: Если запрос выполнился без ошибки, значит логика фильтра правильная.
Как правильно: Нужна проверка на тестовых кейсах, особенно для строк с NULL.
📎 Почему это важно: Большинство ошибок фильтрации — логические, а не синтаксические.

❓ Часто спрашивают на собеседованиях

Вопрос: Почему = NULL не работает в SQL? ✅ Ответ: Потому что NULL — это неизвестное значение, для него используются IS NULL и IS NOT NULL.
Вопрос: Что такое трёхзначная логика в SQL? ✅ Ответ: Это логика с тремя результатами предиката: TRUE, FALSE, UNKNOWN; UNKNOWN часто возникает из-за NULL.
Вопрос: Как WHERE обрабатывает UNKNOWN? ✅ Ответ: Строки с UNKNOWN отбрасываются, как и строки с FALSE.
Вопрос: Почему NOT IN опасен при NULL? ✅ Ответ: Если в списке/подзапросе есть NULL, результат сравнения может стать UNKNOWN и сломать ожидаемую фильтрацию.
Вопрос: Когда нужно добавлять OR field IS NULL? ✅ Ответ: Когда бизнес-правило говорит, что записи с отсутствующим значением тоже должны попасть в выборку.
Вопрос: Зачем скобки при AND и OR, если SQL и так знает приоритет? ✅ Ответ: Скобки делают логику явной, уменьшают риск ошибок и ускоряют ревью.

🚫 Типичные ошибки

Ошибка 1: писать = NULL

Неправильно: WHERE phone = NULL
Правильно: WHERE phone IS NULL
Почему: Обычное сравнение с NULL не даёт TRUE, а значит строка не попадает в результат.

Ошибка 2: забывать скобки в смешанной логике

Неправильно: Смешивать AND и OR без явной группировки.
Правильно: Ставить скобки и явно задавать приоритет.
Почему: Иначе бизнес-условие и фактическая логика SQL могут не совпасть.

Ошибка 3: считать, что <> включает NULL

Неправильно: Ожидать, что field <> 'x' вернёт строки с NULL.
Правильно: Явно добавлять OR field IS NULL, если это требуется бизнес-правилом.
Почему: Для NULL такое сравнение даёт UNKNOWN, и строка отбрасывается.

Ошибка 4: использовать NOT IN, не проверив NULL

Неправильно: NOT IN по списку/подзапросу, где может быть NULL.
Правильно: Сначала проверять источник значений на NULL и выбирать безопасную конструкцию.
Почему: NULL в NOT IN может дать неожиданный результат вплоть до пустой выборки.

Ошибка 5: тестировать фильтр только на «чистых» данных

Неправильно: Проверять запросы только на строках без NULL.
Правильно: Всегда иметь тест-кейсы, где часть полей = NULL.
Почему: Именно такие строки чаще всего ломают реальную фильтрацию в продакшене.

✅ Best Practices

  • Всегда учитывайте NULL как отдельный сценарий, а не «крайний случай».
  • Для nullable-полей сразу проектируйте явные ветки IS NULL / IS NOT NULL.
  • Если булевый флаг может быть NULL, используйте IS TRUE / IS NOT TRUE или явный COALESCE по бизнес-правилу.
  • При смешивании AND/OR используйте скобки даже в «простых» запросах.
  • Перед NOT IN проверяйте, нет ли NULL в источнике значений.
  • Для исключения по подзапросу чаще выбирайте NOT EXISTS, а не NOT IN.
  • Прогоняйте SQL на контрольных наборах с NULL, а не только на «идеальных» данных.
  • В code review требуйте объяснение логики фильтра человеческими словами.

🧾 Заключение

Логические операторы и NULL — это не «дополнительная тема», а ядро корректного WHERE.
Если команда понимает трёхзначную логику SQL, количество ошибок в фильтрации резко падает.

Ключевые мысли

  • AND/OR/NOT управляют структурой условия, но NULL меняет семантику результата.
  • IS NULL / IS NOT NULL — обязательный инструмент, а не опция.
  • UNKNOWN в SQL — нормальное состояние, которое нужно учитывать.
  • Сильная практика фильтрации строится на явной логике и тестах с NULL.
🎯

Проверьте знания

Закрепите материал — пройдите тест по теме «БЛОК 2. Чтение данных — 7. Логические операторы и NULL»

Пройти тест →