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