Как вывести всю номенклатуру в отчет, включая позиции без остатков и оборотов

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов на платформе 1С программисты часто сталкиваются с ситуацией, когда необходимо получить полный список справочника Номенклатура и сопоставить его с данными из регистров накопления (остатками или оборотами). Типичная проблема заключается в том, что после выполнения запроса в отчет попадают только те позиции, по которым были движения, а «пустые» товары исчезают. Разберем подробно, почему так происходит и как правильно составить запрос, чтобы отчет отображал всю номенклатуру.

Анализ классической ошибки при левом соединении

Рассмотрим ситуацию, с которой столкнулся автор вопроса. В его запросе используется левое соединение справочника Номенклатура с временной таблицей, содержащей данные регистра ТоварыВРознице. На первый взгляд, логика верна: ЛЕВОЕ СОЕДИНЕНИЕ должно оставить все записи из левой таблицы (справочника) и добавить к ним данные из правой (регистра), заполнив отсутствующие значения NULL.

Однако проблема кроется в секции ГДЕ. Посмотрим на фрагмент кода:


ИЗ
    ТаблНоменклатуры КАК ТаблНоменклатуры
    ЛЕВОЕ СОЕДИНЕНИЕ ТабРозницы КАК ТабРозницы
    ПО ТаблНоменклатуры.Номенклатура = ТабРозницы.Номенклатура
ГДЕ
    ТабРозницы.Склад = &Склад

Проанализируем, как работает этот фильтр. Если у товара нет остатка на складе, то в поле ТабРозницы.Склад после соединения попадает значение NULL. Условие NULL = &Склад всегда возвращает «Ложь». В результате система исключает все строки, где склад не заполнен, и наше левое соединение фактически превращается во внутреннее (INNER JOIN). Именно поэтому в отчет тянется только та номенклатура, которая есть в регистре.

Решение №1: Перенос условий в параметры виртуальной таблицы

Самый эффективный и правильный с точки зрения производительности 1С способ — это наложение фильтров непосредственно в источнике данных. Если мы используем виртуальную таблицу (например, ОстаткиИОбороты), условия по складу, организации или другим измерениям регистра должны передаваться в параметры этой таблицы.

Рассмотрим, как должен выглядеть исправленный пакет запроса:


// Помещаем всю нужную номенклатуру во временную таблицу
ВЫБРАТЬ
    Номенклатура.Ссылка КАК Номенклатура
ПОМЕСТИТЬ ТаблНоменклатуры
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.ПометкаУдаления = ЛОЖЬ;

////////////////////////////////////////////////////////////////////////////////
// Выбираем данные регистра СРАЗУ с фильтром по складу в параметрах
ВЫБРАТЬ
    ТоварыВРознице.КоличествоКонечныйОстаток КАК Остаток,
    ТоварыВРознице.Склад КАК Склад,
    ТоварыВРознице.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ТабРозницы
ИЗ
    РегистрНакопления.ТоварыВРознице.ОстаткиИОбороты(&ДатаНач, &ДатаКон, , , Склад = &Склад) КАК ТоварыВРознице;

////////////////////////////////////////////////////////////////////////////////
// Финальное соединение без фильтров по правой таблице в ГДЕ
ВЫБРАТЬ
    ТаблНоменклатуры.Номенклатура КАК Номенклатура,
    ЕСТЬNULL(ТабРозницы.Остаток, 0) КАК Остаток,
    &Склад КАК Склад
ИЗ
    ТаблНоменклатуры КАК ТаблНоменклатуры
    ЛЕВОЕ СОЕДИНЕНИЕ ТабРозницы КАК ТабРозницы
    ПО ТаблНоменклатуры.Номенклатура = ТабРозницы.Номенклатура

В этом примере мы использовали ЕСТЬNULL(ТабРозницы.Остаток, 0). Это крайне важная функция: если данных в регистре нет, вместо пустого значения в отчет выведется «0», что позволит корректно суммировать итоги.

Решение №2: Использование условий в секции ПО

Если по каким-то причинам вы не используете виртуальные таблицы (что редкость для задач по остаткам) или соединяете две физические таблицы, условие фильтрации правой таблицы можно перенести из секции ГДЕ в секцию ПО (условие связи). В этом случае фильтрация произойдет до того, как строки будут отсечены из результата левого соединения.

Пример структуры такого запроса:


ВЫБРАТЬ
    Ном.Ссылка,
    Продажи.Количество
ИЗ
    Справочник.Номенклатура КАК Ном
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи КАК Продажи
    ПО Ном.Ссылка = Продажи.Номенклатура
    И Продажи.Склад = &Склад // Условие здесь не превращает LEFT JOIN во внутренний
ГДЕ
    Ном.ЭтоГруппа = ЛОЖЬ

Особенности Универсального отчета и СКД

Выясним причину, почему даже при правильном запросе в Системе компоновки данных (СКД) данные могут пропадать. СКД обладает мощным механизмом оптимизации запросов. Если в настройках отчета пользователь добавляет отбор по полю «Склад», СКД может автоматически добавить это условие в секцию ГДЕ итогового SQL-запроса.

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

  1. Параметры виртуальных таблиц в СКД: В конструкторе запроса на закладке «Компоновка данных» для виртуальных таблиц используйте фигурные скобки {...}. Например: РегистрНакопления.ТоварыВРознице.ОстаткиИОбороты(&ДатаНач, &ДатаКон, , , {Склад.* КАК Склад}). Это позволит СКД подставлять пользовательский отбор именно в параметры виртуальной таблицы, не затрагивая левое соединение.
  2. Роли полей: Если вы выводите остатки, убедитесь, что у полей ресурсов правильно настроены роли (поле «Остатки»). В противном случае СКД может некорректно обрабатывать пустые записи при расчете итогов по группировкам.
  3. Связи наборов данных: Вместо одного сложного запроса попробуйте использовать два набора данных — «НаборДанныхОбъект1» (Справочник) и «НаборДанныхЗапрос2» (Регистр). Соедините их на вкладке «Связи наборов данных». Укажите справочник как источник, а регистр как приемник. Обязательно установите флаг «Обязательная связь» для номенклатуры из справочника, чтобы она всегда присутствовала в результате.

Важность функции ЕСТЬNULL в вычислениях

Рассмотрим подробнее работу с NULL. В 1С любая арифметическая операция с NULL дает в результате NULL. Если ваш отчет предполагает расчет вычисляемых полей (например, Остаток * Цена), и остаток равен NULL, цена также «обнулится» в отчете.

Проанализируем пример правильной обработки в запросе:


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

Такой подход гарантирует, что даже если товара нет на складе или для него не установлена цена, строка не исчезнет из отчета, а в колонках будут стоять нули, что выглядит профессионально и понятно для пользователя.

Итоги и рекомендации

Для того чтобы в отчете всегда выводилась вся номенклатура, придерживайтесь следующих правил:

Применяя эти методы, мы обеспечиваем стабильную работу отчетов и предоставляем пользователям полную информацию об остатках товаров, включая те позиции, которые требуют пополнения или никогда не закупались.

← На главную