При разработке отчетов и обработок в 1С мы часто сталкиваемся с задачей: вывести полный список справочника (например, номенклатуры) и показать наличие этих позиций на складе. Но порой возникает ситуация, когда товары с нулевым остатком просто пропадают из результата запроса, хотя мы ожидали увидеть там ноль или пустое значение. Давайте разберем по шагам эту распространенную проблему на конкретном примере, проанализируем ошибки и выясним, как правильно писать подобные запросы.
Рассмотрим пример запроса, в котором разработчик пытается получить номенклатуру и ее остаток из регистра накопления:
ВЫБРАТЬ ПЕРВЫЕ 30
СпрНоменклатура.Ссылка КАК Ссылка,
СпрНоменклатура.Код КАК Код,
ВЫБОР КОГДА ТоварыОрганизацийОстатки.КоличествоОстаток = "" ТОГДА "0" ИНАЧЕ ТоварыОрганизацийОстатки.КоличествоОстаток КОНЕЦ КАК Поле1
ИЗ Справочник.Номенклатура КАК СпрНоменклатура,
РегистрНакопления.ТоварыОрганизаций.Остатки КАК ТоварыОрганизацийОстатки
ГДЕ НЕ СпрНоменклатура.ЭтоГруппа
И СпрНоменклатура.Ссылка = &Ссылка
И ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура.Ссылка = &Ссылка
В этом коде есть сразу несколько грубых ошибок, которые не позволяют получить правильный результат. Проанализируем каждую из них подробно.
Обратите внимание на секцию ИЗ. Таблицы справочника и регистра перечислены через запятую. В языке запросов 1С это означает полное (кросс) соединение или декартово произведение. Платформа сначала перемножит все строки первой таблицы на все строки второй, и только потом попытается отсечь лишнее с помощью условия в секции ГДЕ. Это крайне неоптимально и является серьезной архитектурной ошибкой. Всегда используйте явные связи таблиц.
В нашем случае, чтобы вывести все товары (даже те, которых нет на остатках), мы должны использовать левое соединение (LEFT JOIN):
ИЗ Справочник.Номенклатура КАК СпрНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыОрганизаций.Остатки КАК ТоварыОрганизацийОстатки
ПО СпрНоменклатура.Ссылка = ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура
Посмотрим на условие: ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура.Ссылка = &Ссылка, расположенное в секции ГДЕ. Это классическая причина того, почему исчезают пустые остатки.
Когда мы делаем левое соединение со справочником, для товаров, которых нет в регистре остатков, в поля регистра подставляется значение NULL (отсутствие значения). Накладывая в секции ГДЕ жесткое условие на присоединяемую таблицу, система неявно требует, чтобы это поле существовало. Но NULL не равен &Ссылка. В итоге условие дает ложь, и левое соединение фактически превращается во внутреннее соединение, полностью отсекая все товары с пустыми остатками.
Самый оптимальный способ фильтровать данные из регистров в 1С — это использовать параметры виртуальных таблиц. Вместо того чтобы вытягивать все остатки, соединять их со справочником и только потом фильтровать по ссылке, передадим отбор непосредственно в саму таблицу остатков:
РегистрНакопления.ТоварыОрганизаций.Остатки(, АналитикаУчетаНоменклатуры.Номенклатура = &Ссылка)
Этот подход колоссально экономит ресурсы СУБД, так как система изначально извлекает остатки только по нужной нам аналитике, а не читает весь регистр целиком.
В исходном запросе разработчик пытался проверить остаток на пустую строку: ТоварыОрганизацийОстатки.КоличествоОстаток = "". Выясним причину, почему это не работает. В 1С строгая типизация данных в запросах. Число нельзя сравнивать со строкой. Более того, при отсутствии связанной записи в регистре, поле получает значение не ноль, не пустую строку и даже не НЕОПРЕДЕЛЕНО. Оно получает значение NULL.
Разберем разницу:
Также важно понимать разницу между выражениями = NULL и ЕСТЬ NULL. Сравнение = NULL вернет сам NULL, а не "Истина" или "Ложь", поэтому использовать знак равенства для проверки на NULL нельзя. Правильно писать ЕСТЬ NULL.
Вместо громоздкой конструкции ВЫБОР КОГДА ... ЕСТЬ NULL ТОГДА 0 ..., используем специализированную функцию ЕСТЬNULL. Она работает быстрее, так как транслируется платформой 1С на уровне SQL в оптимизированные функции СУБД (например, ISNULL или COALESCE).
ЕСТЬNULL(ТоварыОрганизацийОстатки.КоличествоОстаток, 0) КАК Остаток
Соберем все наши знания воедино и напишем корректный запрос, который будет работать быстро и без ошибок выведет товар, даже если его нет на остатке.
ВЫБРАТЬ ПЕРВЫЕ 30
СпрНоменклатура.Ссылка КАК Ссылка,
СпрНоменклатура.Код КАК Код,
ЕСТЬNULL(ТоварыОрганизацийОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
Справочник.Номенклатура КАК СпрНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыОрганизаций.Остатки(, АналитикаУчетаНоменклатуры.Номенклатура = &Ссылка) КАК ТоварыОрганизацийОстатки
ПО СпрНоменклатура.Ссылка = ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура
ГДЕ
НЕ СпрНоменклатура.ЭтоГруппа
И СпрНоменклатура.Ссылка = &Ссылка
В этом запросе мы применили правильное левое соединение, отфильтровали регистр остатков с помощью параметров виртуальной таблицы, исключили фильтрацию присоединенной таблицы в секции ГДЕ и грамотно обработали пустое значение NULL, заменив его на числовой ноль с помощью функции ЕСТЬNULL.