SQL NOT IN вернул пустой результат: как найти NULL и проверить отрицательный отбор

Разбираем исчезнувшие строки в учебном SQL: неизвестное значение, подзапрос, NOT EXISTS, пустой набор и контроль смысла отсутствия записи.

Обновлено 14 мин чтенияУчёба и навыки
Строки программного кода на экране

В таблице есть несдавшие, а запрос никого не показывает

На практикуме по базам данных нужно вывести участников, у которых нет записи о сдаче задания. В списке курса четыре человека, в журнале результатов только двое. Вы используете NOT IN с подзапросом по журналу и получаете пустую таблицу. Синтаксической ошибки нет, соединение с базой работает, фамилии на месте. Вручную видно, что двое должны попасть в ответ. Причиной может быть одно неизвестное значение в столбце, который возвращает подзапрос. Для SQL это меняет логическое условие, даже если остальные коды заполнены правильно.

Рассмотрим вымышленную учебную базу, без настоящих сведений об успеваемости. Участники имеют коды 101, 102, 103 и 104. В журнале сдач записаны коды 101, 103 и NULL. Последняя строка означает, что код участника пока неизвестен. По постановке задачи требуется найти людей без подтверждённого совпадения в журнале. Ожидаемый ответ — 102 и 104. Сначала разберём, почему отрицательный отбор теряет их, затем проверим исправленное условие на нескольких небольших наборах. Скорость запроса здесь вторична: сначала устанавливаем смысл результата. План выполнения имеет смысл сравнивать после совпадения ожидаемых кодов на всех контрольных наборах.

Сначала выполните правую часть отдельно

Откройте именно тот подзапрос, который находится справа от NOT IN, и выполните его отдельно. Не ограничивайтесь просмотром всей таблицы журнала: в запросе могли быть фильтры по заданию, дате или статусу. Вам нужен фактический набор значений после этих условий. Для нашего примера он содержит 101, 103 и NULL. Проверьте также, что подзапрос возвращает код участника, а не номер записи о сдаче. Одинаковый числовой тип двух столбцов не делает их взаимозаменяемыми идентификаторами.

В результате просмотра NULL иногда выглядит пустой ячейкой, специальным значком или словом в скобках. Найдите способ отличить неизвестное значение от пустой строки в вашем клиенте. Строка с текстом «NULL» тоже является другим значением. Для проверки используйте условие IS NULL над нужным столбцом, а не визуальное впечатление от таблицы. Если неизвестных значений нет, рассматриваемая причина не подтверждена. Тогда сравните остальные фильтры и реальный набор участников. Не нужно добавлять новое условие только потому, что оно помогло в чужом примере.

Почему «не найден» не всегда означает TRUE

В SQL логическое выражение может иметь результат TRUE, FALSE или неизвестный результат, представляемый NULL. Обычное сравнение числа с NULL не подтверждает равенство и не подтверждает неравенство. Для кода 102 сравнение с 101 даёт неравенство, с 103 тоже, а с неизвестным кодом итог сравнения остаётся неизвестным. Отрицательный отбор должен установить, что совпадений нет среди всех значений. Пока одно значение неизвестно, обычная проверка NOT IN этого не устанавливает и тоже получает неизвестный результат.

Условие WHERE оставляет строки, для которых выражение истинно. Неизвестный результат не проходит такой отбор. Поэтому 102 и 104 исчезают, хотя среди известных кодов их действительно нет. Для 101 и 103 найдено прямое совпадение, и NOT IN даёт FALSE. В результате не остаётся никого: известные сдачи отвергнуты, а два предполагаемых отсутствия не подтверждены выбранным выражением. Это воспроизводимое следствие логики условия, а не случайное поведение клиента или повреждение таблицы результатов.

Проверьте один код вне общего запроса

На отдельном учебном выражении сравните код 102 со списком из 101 и 103. Отрицательная принадлежность этому списку истинна. Добавьте справа NULL и посмотрите на значение выражения, выведенное как отдельный столбец результата, до применения WHERE. Теперь оно неизвестно. Затем повторите с 101: наличие известного равного значения делает отрицательную принадлежность ложной даже при дополнительном NULL. Такая маленькая проверка разделяет два разных случая и не позволяет запомнить неточное правило «любая операция с NULL всегда возвращает NULL».

Официальная документация PostgreSQL отдельно описывает поведение NOT IN при неизвестном левом значении и при NULL среди результатов подзапроса. Полезно свериться с этим разделом, а затем воспроизвести пример в системе, принятой на курсе. Если преподаватель использует другую СУБД, проверьте её документацию и настройки совместимости. В рабочей заметке назовите конкретную среду. Пример по одному движку не следует превращать в обещание одинакового поведения любых табличных инструментов, где похожее слово «пусто» может обозначать другое состояние.

Не заменяйте неизвестный код произвольным числом

Первое соблазнительное исправление — заменить NULL на ноль или минус один. Запрос может начать возвращать строки, но вы добавите утверждение о данных, которого в исходнике не было. Если выбранное число когда-нибудь станет допустимым кодом участника, замена создаст ложное совпадение. Если специальное значение уже используется по правилам курса, его смысл всё равно требует отдельного описания. Нельзя считать любую необычную цифру безопасной только потому, что в сегодняшнем маленьком примере такого человека нет.

Не удаляйте и всю запись о сдаче ради красивого ответа. Она может хранить важный факт: работа получена, а автор ещё не установлен. Для задачи отрицательного отбора вы решаете, как учитывать неизвестную связь, а не уничтожаете событие. Сохраните исходную строку и сформулируйте правило: в подтверждённые соответствия входят только известные коды. Если преподаватель требует учитывать неподписанные работы особым образом, это меняет постановку отчёта. Покажите ему конкретный случай, прежде чем подписывать кого-либо как окончательно не сдавшего.

Когда достаточно исключить NULL из подзапроса

Если левый список содержит только заполненные коды, а задача требует отсутствия среди известных кодов справа, можно явно ограничить подзапрос условием IS NOT NULL. Для нашего набора справа остаются 101 и 103, после чего NOT IN возвращает 102 и 104. Важно понимать основание этой правки: неизвестная связь не считается подтверждённым совпадением. Это содержательное правило конкретной задачи. Само исчезновение пустого результата ещё не доказывает, что правило подходит для журнала с незавершённой идентификацией работ.

Отдельно проверьте ограничение левого кода. В таблице участников хороший идентификатор обычно задаётся как обязательный ключ, однако в промежуточной выгрузке это не гарантировано. Если слева встречается NULL, фильтрация правой стороны не решает автоматически его судьбу. Такая строка не обозначает участника с установленным кодом. Для неё нужно отдельное решение: отклонить некорректную запись, показать в отчёте проверки или обработать по инструкции. Нельзя молча приравнять её к обычному человеку, для которого проверили наличие сдачи.

Выразите задачу через отсутствие соответствующей строки

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

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

Фильтр задания должен оставаться внутри проверяемого совпадения

Допустим, журнал содержит сдачи трёх лабораторных, а отчёт нужен только по второй. В условие существования входит не только совпадение участника, но и номер нужной лабораторной. Иначе сдача первой работы ошибочно исключит человека из списка по второй. Перед исправлением NULL выпишите полную формулировку соответствия обычными словами: тот же участник, второе задание, принятый статус. Затем сопоставьте её с условиями подзапроса. Каждое ограничение должно иметь видимое место, чтобы ответ не зависел от общего содержимого журнала.

Разберите контроль: у 102 есть запись по первому заданию, а по второму нет. В итог по второму он входит. У 103 есть запись по второму со статусом «черновик». Если постановка признаёт только «принято», он тоже входит до принятия работы. Такие примеры проверяют границу задачи, которую один тест на NULL не покрывает. Не добавляйте статус по собственной догадке: в другом практикуме достаточно любого факта отправки. SQL реализует выбранное правило, но не определяет учебное значение слова «сдал» за автора отчёта.

Повторы справа и пустой набор проверяют разные свойства

Добавьте в учебный журнал вторую запись с кодом 101. Для задачи существования это по-прежнему один участник с найденной сдачей. NOT EXISTS не должен размножить внешнюю строку, поскольку проверяет наличие соответствия, а не выводит каждую подходящую пару. Ожидаемый перечень отсутствующих остаётся прежним. Проверка повторов нужна, чтобы не перепутать отрицательный отбор с задачей подсчёта всех попыток. Если отчёт требует число попыток, одного факта существования будет недостаточно, и понадобится другой расчёт.

Теперь сделайте правый набор полностью пустым, сохранив четыре корректных кода слева. Для нашей задачи должны вернуться все четыре участника. Это важная проверка после добавления фильтра лабораторной: отсутствие сдач по заданию не должно превращаться в отсутствие самих участников. Затем проверьте противоположный случай, когда справа присутствуют все четыре кода. Ответ должен быть пустым по обоснованной причине. Одинаковая пустая таблица может означать и правильный полный охват, и неизвестное условие из-за NULL; различить их позволяет контрольный набор.

Не смешивайте отсутствие совпадения и равенство неизвестных

Условие обычного равенства двух кодов не считает два NULL подтверждённым совпадением. В некоторых задачах требуется специальное сравнение, где два неизвестных значения трактуются одинаково; в PostgreSQL для этого существует IS NOT DISTINCT FROM. Однако для кодов людей такая логика обычно требует особенно ясного основания: два неизвестных автора не становятся одним человеком. Не меняйте оператор равенства только ради уменьшения числа строк. Сначала установите, что означает неизвестное значение в обеих таблицах и разрешает ли задача связывать такие записи.

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

Если используете левое соединение для антиотбора

На занятии могут предложить другой способ: сохранить участников левым соединением с подходящими сдачами и выбрать строки без найденной правой записи. Здесь важно проверять отсутствие по правому полю, которое гарантированно заполнено у настоящего совпадения, например по обязательному идентификатору записи о сдаче. Не выбирайте для этой проверки необязательный комментарий. У найденной сдачи комментарий тоже может отсутствовать, и тогда участник ошибочно окажется в списке несдавших. Имя проверяемого поля должно соответствовать устройству схемы, а не первым видимым пустотам.

Условия номера лабораторной и допустимого статуса при таком построении относятся к определению подходящей правой строки. Если перенести их в общий WHERE без учёта строк, для которых справа ничего не найдено, можно снова потерять нужных участников. Вначале разберите одного человека без сдачи и посмотрите на все поля результата соединения. Затем проверьте человека с принятой сдачей и человека с чужим заданием. Для начинающего студента NOT EXISTS часто ближе к исходной фразе задачи, но выбор способа должен согласовываться с программой курса и понятностью проверки.

Когда логика верна, а рабочая база всё равно удивляет

Учебный набор помогает проверить условие отдельно от доступа к реальным таблицам. Если на нём ожидаемый результат получен, а в рабочем запросе по-прежнему нет строк, проверьте выбранную базу, схему и параметры задания. В клиенте могут быть открыты похожие подключения к разным копиям практикума. Подзапрос также может обращаться к представлению с дополнительным отбором. Запишите число внешних участников и фактический результат правой части в том же подключении. Это локализует расхождение без массовой замены операторов и случайного изменения данных.

Учитывайте момент проверки. Если напарник добавил сдачу между двумя запусками, разные результаты могут отражать изменение журнала, а не ошибку SQL. Для демонстрации используйте стабильную копию вымышленного набора и сохраняйте параметры запуска. Не пытайтесь добиться совпадения с прошлым скриншотом удалением новых записей. На защите достаточно показать исходное состояние примера и объяснить, какое добавление меняет ответ. Когда известная сдача 104 появляется справа, из ожидаемого списка должен уйти именно 104, а 102 остаётся. Это проверяет предметный смысл отрицательного отбора при обновлении данных.

Примите исправление по ожидаемым участникам

Подготовьте небольшую ведомость испытаний: исходные 101 и 103 справа, добавленный NULL, повтор 101, пустой правый набор, полный правый набор и сдача другого задания. Напротив каждого запишите ожидаемые коды до выполнения запроса. Сравнивайте сами коды, а не только количество строк. Два результата одинаковой длины могут содержать разных людей. После каждой проверки верните набор к понятному исходному состоянию, чтобы дополнительные записи одного испытания случайно не повлияли на следующее и не создали непонятное расхождение.

Для сдачи сохраните исходное ошибочное условие, выбранное правило исправления и один короткий разбор трёхзначной логики. В Prepodoff доступны личные учебные материалы и папки. Зарегистрируйтесь, если удобно держать пояснение SQL рядом с конспектом курса. Сам запрос выполняйте и проверяйте в своей СУБД. Завершённая лабораторная показывает, почему конкретные строки входят в результат, как учитывается неизвестная связь и что происходит на пустом наборе. Такое объяснение помогает восстановить решение при следующей похожей задаче, даже если названия таблиц и условия задания изменятся.

Была полезна статья?

Читайте также

Пригодится для подготовки