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

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

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

Анализ типичных ошибок в запросах

Рассмотрим ситуацию, когда разработчик пытается соединить остатки и цены, используя ПОЛНОЕ СОЕДИНЕНИЕ. Проанализируем два варианта написания кода, которые часто встречаются у новичков, и выясним, почему они работают по-разному.

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

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

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

Почему нельзя использовать соединение по наименованию

Проанализируем еще одну критическую ошибку — соединение таблиц по текстовым полям, таким как Наименование. Рассмотрим пример:


ПО ТоварыНаСкладахОстатки.Товар.Наименование = ЦеныТоваровСрезПоследних.Товар.Наименование

Никогда не используйте этот подход в реальных задачах по следующим причинам:

  1. Производительность: Сравнение строк в СУБД работает в десятки раз медленнее, чем сравнение ссылок (GUID).
  2. Индексация: Поле Наименование обычно не проиндексировано для целей соединения, что заставляет сервер проводить полное сканирование таблиц.
  3. Дубли: В справочнике Номенклатура могут существовать разные товары с одинаковыми названиями. Соединение по строке приведет к некорректному задвоению данных в отчете.

Всегда выполняйте соединение по ссылочным полям: Товар = Товар или Номенклатура = Номенклатура.

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

Для того чтобы отчет работал быстро даже на больших объемах данных, рекомендуется использовать механизм временных таблиц. Это позволяет структурировать запрос и упростить работу оптимизатору СУБД. Разберем алгоритм построения такого запроса.

Шаг 1: Получение остатков. Сначала мы выбираем остатки по конкретному складу и помещаем их во временную таблицу ВТ_Остатки.


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Товар КАК Товар,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК ТоварыНаСкладахОстатки
;

Шаг 2: Получение цен. Теперь выберем цены нужного типа. Чтобы не тянуть лишние данные, мы можем ограничить выборку цен только теми товарами, которые есть в нашей временной таблице остатков.


ВЫБРАТЬ
    ЦеныТоваровСрезПоследних.Товар КАК Товар,
    ЦеныТоваровСрезПоследних.Цена КАК Цена
ПОМЕСТИТЬ ВТ_Цены
ИЗ
    РегистрСведений.ЦеныТоваров.СрезПоследних(&Период, ТипЦены = &ТипЦены 
        И Товар В (ВЫБРАТЬ ВТ_Остатки.Товар ИЗ ВТ_Остатки)) КАК ЦеныТоваровСрезПоследних
;

Шаг 3: Финальное соединение. Соединяем остатки с ценами. Здесь крайне важно использовать функцию ЕСТЬNULL, чтобы при отсутствии цены сумма не превратилась в пустое значение, и отчет не «поплыл».


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

Нюансы работы с виртуальной таблицей СрезПоследних

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

Поэтому предварительная выгрузка цен во временную таблицу (как показано выше) — это не просто вопрос красоты кода, а требование к производительности системы.

Обработка пустых значений и точность

Проанализируем ситуацию с расчетом суммы. Помимо функции ЕСТЬNULL, полезно использовать оператор ВЫРАЗИТЬ. Это гарантирует, что тип данных в итоговой колонке будет числовым с заданной точностью, что важно для корректного отображения в схеме компоновки данных (СКД) или печатных формах.

Пример корректного расчета поля суммы:


ВЫРАЗИТЬ(Остатки.КоличествоОстаток * ЕСТЬNULL(Цены.Цена, 0) КАК ЧИСЛО(15, 2)) КАК Сумма

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

Итоги

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

Соблюдение этих правил позволит вашим отчетам работать стабильно и выдавать корректные данные даже при значительном росте базы данных.

← На главную