Как в запросе 1С проверить поле на заполненность

· 10 мин

Содержание

Чтобы в запросе проверить поле на заполненность, выбор зависит от того, что именно пусто. Для полей из присоединённых таблиц (ЛЕВОЕ СОЕДИНЕНИЕ) и пропущенных данных регистров работают операторы ЕСТЬ NULL и ЕСТЬ НЕ NULL. Для пустых ссылок справочников и документов — сравнение со ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка). Для строк и дат — сравнение с "" и ДАТАВРЕМЯ(1,1,1).

Главная ошибка новичка — писать ГДЕ Поле = NULL. Это условие не отберёт ни одной строки никогда. NULL нельзя сравнивать знаком равенства, для него есть отдельный оператор. Ниже — как устроена пустота в языке запросов, чем ЕСТЬ NULL отличается от функции ЕСТЬNULL, как это влияет на индексы и какие ловушки встречаются чаще всего.

Три разных пустоты: NULL, Неопределено и пустое значение

В языке запросов 1С «пусто» — это не одно состояние, а минимум три разных, и путать их нельзя.

🔹 NULL — отсутствие данных как таковых. Поля нет, строки нет, присоединение не нашло пару. NULL не равен нулю, не равен пустой строке и не равен сам себе.

🔹 Неопределено (NULL ≠ Неопределено) — специальное значение составного типа. Появляется, например, когда поле составного типа не заполнено, или при разыменовании реквизита у пустой ссылки.

🔹 Пустое значение типа — заполненное поле, но «нулевое» для своего типа: пустая ссылка справочника, строка "", число 0, пустая дата ДАТАВРЕМЯ(1,1,1).

Разница практическая. Если поле Поставщик в документе просто не выбрали — там лежит пустая ссылка, а не NULL. А вот если вы присоединяете к этому документу регистр ЛЕВЫМ СОЕДИНЕНИЕМ и записи там не оказалось — поля регистра получат именно NULL. Поэтому проверка «не выбран поставщик» и проверка «нет движения по регистру» пишутся по-разному.

Запомните правило: NULL почти всегда приходит из ЛЕВОГО/ПОЛНОГО СОЕДИНЕНИЯ и из виртуальных таблиц остатков, когда данных по измерению нет. Пустая ссылка — из самого реквизита объекта.

Откуда в результате берётся NULL

Прежде чем проверять, полезно понимать источник. NULL в выборке возникает в нескольких ситуациях:

  1. Левое или полное соединение без пары. Строка левой таблицы есть, а в правой совпадения нет — все поля правой таблицы станут NULL.
  2. Виртуальные таблицы остатков и оборотов. Если по номенклатуре не было движений, КоличествоОстаток вернётся как NULL, а не как 0.
  3. Поле составного типа, у которого разыменовали реквизит несуществующего типа.
  4. Агрегатные функции СУММА, МАКСИМУМ по пустому набору строк после группировки в отдельных сценариях.

Самый частый случай — первый. Классический запрос «остатки по всей номенклатуре, включая ту, что лежит мёртвым грузом с нулём»:

ВЫБРАТЬ
    Номенклатура.Наименование КАК Наименование,
    Остатки.КоличествоОстаток КАК Остаток
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
    ПО Остатки.Номенклатура = Номенклатура.Ссылка

По товарам без остатка поле Остаток придёт как NULL. Если потом прибавить к нему число — получите снова NULL, а не сумму. Отсюда и растут все дальнейшие приёмы.

Синтаксис операторов ЕСТЬ NULL и ЕСТЬ НЕ NULL

Проверить, что поле пусто в смысле NULL, можно только через оператор ЕСТЬ NULL. Обратная проверка — ЕСТЬ НЕ NULL.

Найти товары, по которым вообще нет движений:

ВЫБРАТЬ
    Номенклатура.Наименование КАК Наименование
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
    ПО Остатки.Номенклатура = Номенклатура.Ссылка
ГДЕ
    Остатки.КоличествоОстаток ЕСТЬ NULL

Зеркальный случай — только те, по кому движения были:

ГДЕ
    Остатки.КоличествоОстаток ЕСТЬ НЕ NULL

Ещё один типовой приём — поиск «сирот»: документы-основания, по которым не создали дочерний документ. Например, заказы покупателей без реализации:

ВЫБРАТЬ
    Заказы.Номер КАК Номер,
    Заказы.Дата КАК Дата
ИЗ
    Документ.ЗаказКлиента КАК Заказы
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
    ПО Реализация.ЗаказКлиента = Заказы.Ссылка
ГДЕ
    Реализация.Ссылка ЕСТЬ NULL

Проверяйте на ЕСТЬ NULL поле Ссылка правой таблицы, а не какой-то реквизит. Реквизит теоретически может быть пустым и при наличии строки, а вот Ссылка при отсутствии соединения гарантированно NULL.

Названия таблиц и реквизитов (ЗаказКлиента, РеализацияТоваровУслуг) отличаются между конфигурациями. Проверьте имена в конструкторе запроса своей базы — логика операторов от этого не меняется.

Функция ЕСТЬNULL: подстановка значения вместо пустоты

Оператор ЕСТЬ NULL только проверяет. Функция ЕСТЬNULL (в англоязычной раскладке ISNULL) — заменяет NULL на нужное значение прямо в выборке. Синтаксис:

ЕСТЬNULL(<Выражение>, <ЗначениеПоУмолчанию>)

Если <Выражение> не NULL — вернётся оно само. Если NULL — вернётся второй аргумент. Тот же запрос по остаткам, но с нулями вместо пустот:

ВЫБРАТЬ
    Номенклатура.Наименование КАК Наименование,
    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
    ПО Остатки.Номенклатура = Номенклатура.Ссылка

Теперь товары без движений покажут честный 0, и по полю можно спокойно считать суммы, сортировать, сравнивать с числом.

Важная деталь про типы: второй аргумент задаёт тип результата. Если написать ЕСТЬNULL(Поле, 0), поле станет числовым. Для суммовых полей подставляйте число, для дат — дату, для строк — строку. Подстановка числа туда, где ждут строку, приведёт к ошибке совместимости типов.

Когда ЕСТЬNULL не хватает — помогает конструкция ВЫБОР КОГДА. Она гибче: позволяет вернуть разные значения по разным условиям.

ВЫБОР
    КОГДА Остатки.КоличествоОстаток ЕСТЬ NULL
        ТОГДА "Нет на складе"
    КОГДА Остатки.КоличествоОстаток = 0
        ТОГДА "Нулевой остаток"
    ИНАЧЕ "В наличии"
КОНЕЦ

Здесь видно разницу, которую ЕСТЬNULL стирает: NULL (движений не было вовсе) и 0 (движения были, но свелись в ноль) — это разные состояния, и отчёт иногда должен их различать.

Пустые ссылки, строки и даты — здесь NULL ни при чём

Частая путаница: разработчик пишет ГДЕ Договор ЕСТЬ NULL, рассчитывая поймать документы без договора, и получает пустой результат. Реквизит объекта не бывает NULL — если его не заполнили, там лежит пустая ссылка.

Для таких проверок нужен другой инструмент:

Пустая ссылка справочника или документа — сравнение со специальным пустым значением:

ГДЕ
    Товары.Поставщик = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)

Обратная проверка «поставщик заполнен» — через <>:

ГДЕ
    Товары.Поставщик <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)

Пустая строка:

ГДЕ
    Контрагенты.ИНН = ""

Пустая дата — это не NULL, а минимально возможная дата платформы:

ГДЕ
    Договоры.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1)

Разберём таблицей, когда что применять.

Что проверяемПравильный способНеверно
Поле присоединённой таблицыПоле ЕСТЬ NULLПоле = NULL
Реквизит-ссылка объекта= ЗНАЧЕНИЕ(…ПустаяСсылка)ЕСТЬ NULL
Строковый реквизит= ""ЕСТЬ NULL
Дата= ДАТАВРЕМЯ(1,1,1)ЕСТЬ NULL

Отдельный разговор — табличные части. Если нужно отобрать документы, у которых таблица товаров пуста, напрямую «количество строк» в условии ГДЕ не проверить. Приём — левое соединение с табличной частью и проверка её Ссылки на ЕСТЬ NULL:

ВЫБРАТЬ РАЗЛИЧНЫЕ
    Поступление.Ссылка КАК Документ
ИЗ
    Документ.ПоступлениеТоваровУслуг КАК Поступление
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
    ПО Товары.Ссылка = Поступление.Ссылка
ГДЕ
    Товары.Ссылка ЕСТЬ NULL

Здесь NULL снова работает по назначению — потому что речь о соединении, а не о реквизите.

Чтобы не держать всё это в голове, определите по шагам, что у вас за пустота:

Что именно вы проверяете на заполненность?

Как проверки влияют на производительность и индексы

Проверка на заполненность кажется безобидной, но на больших таблицах легко посадить запрос.

Функция поверх поля в условии ломает индекс. Если написать в ГДЕ условие вида ЕСТЬNULL(Поле, 0) = 0, СУБД не сможет использовать индекс по этому полю — ей придётся вычислять функцию для каждой строки. Это полный перебор. Для отбора по пустоте оставляйте поле «чистым»: Поле ЕСТЬ NULL индекс использовать может, ЕСТЬNULL(Поле, 0) = 0 — нет.

ЕСТЬNULL уместна в выборке, а не в отборе. Подставлять нули для вывода — нормально. Фильтровать по обёрнутому в функцию полю — плохо. Если нужен и отбор, и замена, фильтруйте по ЕСТЬ NULL / ЕСТЬ НЕ NULL, а ЕСТЬNULL используйте только в списке полей.

Проверка ЕСТЬ NULL после соединения — частый антипаттерн вместо ПРАВОЕ/подзапроса. Поиск сирот через ЛЕВОЕ СОЕДИНЕНИЕ + ЕСТЬ NULL читается просто, но на миллионах строк соединение дороже, чем отбор через НЕ В (ВЫБРАТЬ …) по индексированному полю. На небольших справочниках разница незаметна, на документообороте за годы — чувствительна.

Правило простое: проверяйте на ЕСТЬ NULL только то поле, по которому реально нет данных (обычно Ссылка правой таблицы), и не навешивайте функций на поля, по которым идёт отбор.

Типичные ошибки и как их не совершать

🔸 Поле = NULL и Поле <> NULL. Самая частая. Равенство с NULL всегда даёт неопределённый результат, строки не отбираются. Только ЕСТЬ NULL / ЕСТЬ НЕ NULL.

🔸 Проверка реквизита-ссылки через ЕСТЬ NULL. Реквизит объекта не бывает NULL, он бывает пустой ссылкой. Результат — пустая выборка, хотя незаполненные документы есть.

🔸 Арифметика с NULL без защиты. Остатки.Приход - Остатки.Расход, где одно из полей NULL, даст NULL, а не разницу. Оборачивайте каждое слагаемое: ЕСТЬNULL(Приход, 0) - ЕСТЬNULL(Расход, 0).

🔸 ЕСТЬNULL с несовместимым типом второго аргумента. ЕСТЬNULL(Дата, 0) или ЕСТЬNULL(Строка, 0) вызовут ошибку совместимости типов. Подставляйте значение того же типа, что поле.

🔸 Отбор по полю под функцией. ГДЕ ЕСТЬNULL(Сумма, 0) > 100 отрабатывает, но без индекса. Если по полю не бывает NULL — уберите обёртку; если бывает — продумайте соединение.

🔸 Путаница NULL и Неопределено в полях составного типа. Это разные значения; ЕСТЬ NULL поймает NULL, но не Неопределено. Для составных типов иногда нужны обе проверки.

Проверьте себя — три коротких вопроса на понимание:

Проверьте себя
Как отобрать строки, где поле присоединённой таблицы пусто?
Что вернёт `ЕСТЬNULL(Остаток, 0)`, если движений по товару не было?
Как проверить, что не выбран контрагент в реквизите документа?

Практические примеры из работы

Сведём приёмы в задачи, которые встречаются постоянно.

Остатки по всем товарам, включая нулевые. Левое соединение справочника с виртуальной таблицей остатков, поле обёрнуто в ЕСТЬNULL(…, 0). Иначе товары без движений либо выпадут (при внутреннем соединении), либо дадут NULL в сумме.

Контрагенты без заполненного ИНН. Это реквизит-строка, поэтому ГДЕ Контрагенты.ИНН = "". Плюс можно добавить ИЛИ Контрагенты.ИНН ЕСТЬ NULL — на случай, если поле приходит из соединения.

Договоры без даты окончания (бессрочные). ГДЕ Договоры.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1). Пустая дата — валидное значение «не указано».

Номенклатура без указанной группы или вида. Реквизит-ссылка: ГДЕ Номенклатура.ВидНоменклатуры = ЗНАЧЕНИЕ(Справочник.ВидыНоменклатуры.ПустаяСсылка).

Заказы без отгрузки. Левое соединение с документом реализации и ГДЕ Реализация.Ссылка ЕСТЬ NULL. На больших объёмах — через НЕ В (ВЫБРАТЬ ЗаказКлиента ИЗ Документ.РеализацияТоваровУслуг …).

Общая логика выбора метода одна: сначала определите, откуда взялось поле. Из соединения или виртуальной таблицы — работаете с NULL. Из самого объекта — с пустым значением типа. Перепутать эти два мира — и есть корень большинства «запрос почему-то ничего не находит».

Частые вопросы

Чем `ЕСТЬ NULL` отличается от функции `ЕСТЬNULL`?
ЕСТЬ NULL — это оператор условия, он возвращает истину или ложь и ставится в ГДЕ или ВЫБОР КОГДА. ЕСТЬNULL(Поле, Значение) — функция, она не проверяет, а подставляет указанное значение вместо NULL прямо в выбранное поле.
Почему условие `ГДЕ Поле = NULL` не работает?
NULL означает «значение неизвестно», поэтому любое сравнение с ним через = или <> даёт не истину и не ложь, а неопределённый результат. Такие строки в выборку не попадают. Для NULL существует только ЕСТЬ NULL и ЕСТЬ НЕ NULL.
NULL и пустая ссылка — это одно и то же?
Нет. Пустая ссылка — заполненное значение справочного типа, «ничего не выбрано». NULL — отсутствие данных как такового, обычно из незавершённого соединения. Проверяются они разными средствами и путать их нельзя.
Как посчитать разницу двух полей, если одно может быть NULL?
Оберните каждое поле в ЕСТЬNULL с нулём: ЕСТЬNULL(Приход, 0) - ЕСТЬNULL(Расход, 0). Без этого любое NULL-слагаемое обнулит весь результат до NULL.
Замедляет ли `ЕСТЬNULL` запрос?
В списке выбираемых полей — практически нет. В условии отбора ГДЕ — да: функция поверх поля мешает использовать индекс, и СУБД перебирает все строки. Для отбора фильтруйте по ЕСТЬ NULL, а ЕСТЬNULL держите только в полях выборки.