Как вывести номенклатуру в отчет, если для нее не задана цена: исправляем пустые результаты в запросе 1С

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

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

Анализ проблемы: почему исчезают данные

Рассмотрим ситуацию, представленную в вопросе. Автор использует ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN) между временной таблицей с номенклатурой и временной таблицей с ценами. Механика внутреннего соединения такова: в результат попадают только те записи, которые есть в обеих таблицах одновременно.

Если в вашей базе заведены только розничные цены, а в параметрах запроса вы выбираете «Закупочную», то временная таблица цен будет пустой. В результате внутреннее соединение с пустой таблицей всегда даст пустой результат, даже если в справочнике номенклатуры тысячи записей. Чтобы этого избежать, необходимо использовать другой тип связи.

Решение №1: Использование левого соединения

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

Проанализируем исправленный фрагмент кода:


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

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

Решение №2: Оптимизация условий соединения

Разберем типичную ошибку новичков, которая замедляет работу запроса. В исходном коде автора соединение происходило по полю .Ссылка:

ПО ДанныеНоменклатурыВТ.Наименование.Ссылка = ЦенаНоменклатурыВТ.Номенклатура.Ссылка

Выясним причину, почему так делать не стоит. Поля Наименование (если это ссылка на справочник) и Номенклатура уже содержат ссылки. Обращение через точку к полю .Ссылка заставляет платформу выполнять лишнее неявное соединение в SQL-запросе. Рассмотрим правильный вариант:

Правильно: ПО ДанныеНоменклатурыВТ.Наименование = ЦенаНоменклатурыВТ.Номенклатура

Решение №3: Повышение производительности и безопасности

Чтобы наш запрос работал быстро даже в крупных базах (например, в «1С:ERP» или «1С:Управление торговлей»), применим несколько профессиональных приемов:

  1. Индексация временных таблиц. Если в выборке много позиций, поиск цены по каждой строке будет долгим. Добавим индексацию по полям связи.
  2. Использование ключевого слова РАЗРЕШЕННЫЕ. Это обеспечит корректную работу у пользователей с ограниченными правами (RLS).
  3. Фильтрация помеченных на удаление. Обычно в отчеты не должны попадать «удаленные» товары.

Посмотрим на пример оптимизированного полного запроса:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
|   Номенклатура.Ссылка КАК Ссылка,
|   Номенклатура.Артикул КАК Артикул,
|   Номенклатура.Родитель КАК Группа
|ПОМЕСТИТЬ ДанныеНоменклатурыВТ
|ИЗ
|   Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|   Номенклатура.ЭтоГруппа = ЛОЖЬ
|   И Номенклатура.ПометкаУдаления = ЛОЖЬ
|ИНДЕКСИРОВАТЬ ПО Ссылка
|;
|
|ВЫБРАТЬ
|   ЦеныСрезПоследних.Цена КАК Цена,
|   ЦеныСрезПоследних.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ЦенаНоменклатурыВТ
|ИЗ
|   РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ВидЦены = &ВидЦены) КАК ЦеныСрезПоследних
|ИНДЕКСИРОВАТЬ ПО Номенклатура
|;
|
|ВЫБРАТЬ
|   ДанныеВТ.Ссылка КАК Наименование,
|   ДанныеВТ.Артикул КАК Артикул,
|   ДанныеВТ.Группа КАК ГруппаНоменклатуры,
|   ЕСТЬNULL(ЦенаВТ.Цена, 0) КАК Цена
|ИЗ
|   ДанныеНоменклатурыВТ КАК ДанныеВТ
|       ЛЕВОЕ СОЕДИНЕНИЕ ЦенаНоменклатурыВТ КАК ЦенаВТ
|       ПО ДанныеВТ.Ссылка = ЦенаВТ.Номенклатура";

Запрос.УстановитьПараметр("Период", Объект.ПериодВыбораЦен);
Запрос.УстановитьПараметр("ВидЦены", Объект.ВидЦеныНоменклатуры);

Дополнительные возможности: Приоритеты цен

Иногда задача стоит сложнее: если нет закупочной цены, нужно показать розничную. В этом случае мы можем выполнить два левых соединения (к разным временным таблицам цен) и использовать конструкцию ВЫБОР. Проанализируем пример логики:

ВЫБОР КОГДА ЦенаЗакупочная.Цена ЕСТЬ NULL ТОГДА ЦенаРозничная.Цена ИНАЧЕ ЦенаЗакупочная.Цена КОНЕЦ

Этот подход гарантирует, что поле «Цена» в вашем отчете будет максимально информативным, даже если данные в системе заполнены частично.

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

← На главную