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.Синтаксис при этом часто валиден, но бизнес-результат ошибочный.
Решение — строить фильтр по шагам:
- сначала базовый предикат,
- затем логические связки со скобками,
- отдельная обработка
NULLчерезIS NULL / IS NOT NULL, - проверка поведения на тестовых кейсах с
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 требуйте объяснение логики фильтра человеческими словами.
🧾 Заключение
Логические операторы и
Если команда понимает трёхзначную логику SQL, количество ошибок в фильтрации резко падает.
NULL — это не «дополнительная тема», а ядро корректного WHERE.Если команда понимает трёхзначную логику SQL, количество ошибок в фильтрации резко падает.
Ключевые мысли
AND/OR/NOTуправляют структурой условия, ноNULLменяет семантику результата.IS NULL / IS NOT NULL— обязательный инструмент, а не опция.UNKNOWNв SQL — нормальное состояние, которое нужно учитывать.- Сильная практика фильтрации строится на явной логике и тестах с
NULL.