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

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

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

Суть проблемы: зачем создают лишние временные таблицы?

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

Часто решение выглядит следующим образом:

  1. В первом запросе данные выбираются из таблицы, группируются и помещаются во временную таблицу (ВТ).
  2. Во втором запросе данные выбираются из ВТ, и уже там происходит деление полей.

Это связано с пониманием порядка выполнения SQL-запроса. Поскольку вычисление полей в секции ВЫБРАТЬ происходит после группировки, мы не можем просто написать Стоимость / Количество, если эти поля являются агрегатными суммами, без явного указания агрегации. Однако создавать для этого целую временную таблицу избыточно.

Решение 1: Прямая выборка с агрегатными функциями

Можно ли обойтись без временной таблицы? Безусловно. Язык запросов 1С (как и SQL) позволяет выполнять арифметические операции над результатами агрегатных функций прямо в секции выбора полей.

Вместо того чтобы сначала суммировать поля в ВТ, а потом делить их, мы можем написать формулу вида СУММА(Поле1) / СУММА(Поле2).

Однако здесь нас подстерегает классическая ошибка: деление на ноль. Если в выборку попадет запись, где количество равно 0 (например, корректировка стоимости без движения количества), запрос завершится с ошибкой.

Для решения этой задачи используем конструкцию ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ. Посмотрим на правильный пример кода:


ВЫБРАТЬ
    Себестоимость.Регистратор КАК Регистратор,
    Себестоимость.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
    
    // Безопасное вычисление Цены без НДС
    ВЫБОР 
        КОГДА СУММА(Себестоимость.Количество) = 0 
        ТОГДА 0 
        ИНАЧЕ СУММА(Себестоимость.СтоимостьБезНДС) / СУММА(Себестоимость.Количество) 
    КОНЕЦ КАК ЦенаБезНДС,
    
    // Безопасное вычисление Цены с НДС
    ВЫБОР 
        КОГДА СУММА(Себестоимость.Количество) = 0 
        ТОГДА 0 
        ИНАЧЕ СУММА(Себестоимость.Стоимость) / СУММА(Себестоимость.Количество) 
    КОНЕЦ КАК ЦенаСНДС

ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК Себестоимость
ГДЕ
    Себестоимость.Регистратор = &Ссылка
СГРУППИРОВАТЬ ПО
    Себестоимость.Регистратор,
    Себестоимость.АналитикаУчетаНоменклатуры.Номенклатура

Проанализируем этот код:

Решение 2: Использование виртуальных таблиц

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

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

  1. Автоматическая свертка: Виртуальная таблица оборотов сама группирует записи по измерениям. Вам не нужно писать секцию СГРУППИРОВАТЬ ПО, если вы не добавляете специфические поля.
  2. Производительность: Платформа 1С использует сохраненные агрегаты (итоги) для быстрого получения данных, что работает быстрее, чем сканирование таблицы движений.
  3. Читаемость: Код становится компактнее.

Пример оптимизированного запроса через виртуальную таблицу:


ВЫБРАТЬ
    СебестоимостьОбороты.Регистратор КАК Регистратор,
    СебестоимостьОбороты.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
    
    // В виртуальной таблице поля уже называются "Оборот", "Приход" или "Расход"
    // Здесь предполагается, что мы берем общий оборот
    ВЫБОР 
        КОГДА СебестоимостьОбороты.КоличествоОборот = 0 
        ТОГДА 0 
        ИНАЧЕ СебестоимостьОбороты.СтоимостьОборот / СебестоимостьОбороты.КоличествоОборот 
    КОНЕЦ КАК ЦенаСредняя

ИЗ
    РегистрНакопления.СебестоимостьТоваров.Обороты(
        , , // Период (начало, конец)
        Регистратор, // Периодичность - авто, запись, регистратор и т.д.
        Регистратор = &Ссылка // Условие отбора
    ) КАК СебестоимостьОбороты

Важное замечание: Обратите внимание на параметры виртуальной таблицы. Третий параметр — периодичность. Если вам нужен именно разрез по документам, необходимо указать периодичность Регистратор или Запись.

Когда стоит оставить временную таблицу?

Несмотря на возможность всё написать в одном запросе, иногда использование временных таблиц (как в исходном вопросе автора) является более правильным с точки зрения архитектуры и производительности SQL-сервера.

Рассмотрим ситуации, когда "один большой запрос" проигрывает:

  1. Сложные вычисления и оптимизатор СУБД: Если вы делаете ВЫБОР КОГДА СУММА(...), SQL-серверу может потребоваться вычислить сумму дважды (для проверки условия и для деления), если его оптимизатор недостаточно умен. Во временной таблице сумма считается один раз.
  2. Индексация: Временную таблицу можно проиндексировать. Если полученные данные ("_Себестоимость") в дальнейшем будут соединяться с другими большими таблицами, индекс по полю Номенклатура во временной таблице критически важен для скорости. Вложенный запрос или сложная выборка не имеют индексов.
  3. Читаемость и отладка: Если логика расчета цены очень сложная, разбиение на этапы ("Собрали данные" -> "Рассчитали") упрощает поддержку кода. В консоли запросов можно посмотреть результат первого шага.

Выводы и рекомендации

Подведем итоги анализа темы:

  1. Да, обойтись без временной таблицы можно. Используйте агрегатные функции прямо в списке полей выборки: СУММА(Стоимость) / СУММА(Количество).
  2. Обязательно обрабатывайте деление на ноль. Используйте конструкцию ВЫБОР КОГДА ... = 0 ТОГДА 0 ИНАЧЕ ... КОНЕЦ.
  3. Предпочитайте виртуальные таблицы. Для получения данных из регистров накопления лучше использовать .Обороты или .ОстаткиИОбороты, вместо прямой таблицы движений, если это соответствует логике задачи.
  4. Не бойтесь временных таблиц. Если запрос становится слишком громоздким или планируются дальнейшие соединения, использование временной таблицы (как в исходном коде автора) — это хорошая практика ("Best Practice") в мире 1С для помощи оптимизатору СУБД.
← На главную