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

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

При получении остатков номенклатуры из конкретного документа разработчики часто сталкиваются с ситуацией, когда итоговое количество товара в запросе умножается на 2, 3 и более раз. Выясним причину этой проблемы, разберем типичные ошибки в конструкциях языка запросов 1С и рассмотрим правильные варианты решения задачи.

Причина возникновения задвоения показателей

Проанализируем исходную ситуацию. Разработчик пытается соединить виртуальную таблицу остатков с табличной частью документа ЗаказПокупателя.Товары с помощью внутреннего соединения по полю Номенклатура.

Если в документе одна и та же позиция номенклатуры добавлена в двух разных строках (например, с разной ценой, характеристикой или строкой комментария), происходит размножение строк. СУБД выполняет соединение по принципу «один ко многим», создавая декартово произведение строк. В результате запись остатка из регистра объединяется с каждой строкой документа, где встречается данное номенклатурное позиционирование.

Использование ключевого слова РАЗЛИЧНЫЕ в секции ВЫБРАТЬ не решает эту проблему, так как конструкция СУММА(ОстаткиТоваровКомпании.КоличествоОстаток) отрабатывает на этапе группировки по всем физически полученным в результате соединения строкам. В итоге остаток суммируется ровно столько раз, сколько строк с этой номенклатурой содержится в документе.

Анализ неоптимальных конструкций в коде

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

  1. Обращение к реквизитам через точку: Конструкции вида ЗаказПокупателяТовары.Ссылка.Ссылка или ОстаткиТоваровКомпании.Номенклатура.Ссылка являются избыточными. Обращение к реквизиту Ссылка у объекта, который сам является ссылкой, провоцирует неявные соединения с таблицами базы данных.
  2. Фильтрация вне параметров виртуальной таблицы: Условия по складам (ОстаткиТоваровКомпании.Склад.Отделение и ОстаткиТоваровКомпании.Склад.ЭтоМол) вынесены в секцию ГДЕ. Из-за этого платформа сначала рассчитывает остатки по всем складам и номенклатурным позициям в базе, а фильтрацию производит уже после получения всей таблицы остатков.
  3. Соединение с виртуальной таблицей на верхнем уровне: Прямое соединение табличной части документа с виртуальной таблицей остатков мешает оптимизатору СУБД построить эффективный план выполнения запроса.

Способы решения проблемы

Разберем по шагам корректные подходы к написанию запроса, исключающие задвоение данных и повышающие скорость работы с базой данных.

Вариант 1. Использование вложенного запроса в параметрах виртуальной таблицы

Если задача заключается только в получении остатков по номенклатуре конкретного заказа, соединять таблицы в секции ИЗ не требуется. Достаточно передать список номенклатуры документа прямо в параметр отбора виртуальной таблицы Остатки().

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


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

Преимущества данного подхода:

  1. Отбор номенклатуры происходит на уровне виртуальной таблицы, что исключает дублирование строк.
  2. Условие по складам передается через параметр Склад В (&МассивСкладов), который предварительно формируется на языке 1С. Это предотвращает выборку лишних данных из СУБД.
  3. Запрос выполняется максимально быстро, так как виртуальная таблица считывает остатки только по нужным позициям.

Вариант 2. Использование временной таблицы с группировкой

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

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ЗаказТовары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_НоменклатураЗаказа
ИЗ
    Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
ГДЕ
    ЗаказТовары.Ссылка = &Заказ
;

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

При использовании временных таблиц соблюдаются стандарты разработки 1С: исключаются декартовы произведения, а виртуальная таблица получает фильтр по номенклатуре из сформированной ВТ.

Рекомендации по передаче параметров

  1. Передача даты остатков: Чтобы учесть точный момент времени и движения документа, передавайте в параметр &ДатаАнализа объект типа Граница или МоментВремени. Если передать значение типа Дата, остатки будут рассчитаны на начало указанной секунды, без учета движений, сделанных в эту же секунду.
  2. Подготовка списка складов: Для исключения обращений через точку в условиях запроса (например, Склад.Отделение = &Отделение) рекомендуется заранее получить массив ссылок на склады с помощью отдельного запроса или функции 1С и передать готовый массив в параметр &МассивСкладов.
← На главную