Как реализовать условие при выборе полей и суммировании в запросе 1С 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

Почему не работает тернарный оператор в запросе?

Проанализируем типичную ошибку, которую допускают разработчики. Зачастую они пытаются использовать встроенный тернарный оператор ?() прямо в тексте запроса, чтобы динамически присвоить знак результату в зависимости от условия. Посмотрим на пример такого нерабочего кода:


ТекстЗапроса="//{{ЗАПРОС(ПриходныеНакладные)
|Период с ДатаНачала по ДатаОкончания;
|Товар = Документ.ПриходнаяНакладная.Товар;
|ЭтоВозврат = Документ.ПриходнаяНакладная.ВидОперации;
|Количество = ?(ЭтоВозврат=0, 1, -1) * Документ.ПриходнаяНакладная.Количество;
|Функция СуммаКоличество = Сумма(Количество);
|Группировка Товар без групп;
|"//}}ЗАПРОС

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

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

Решение 1: Использование конструкции КОГДА и вычисление при выводе

Рассмотрим самый популярный и надежный способ обойти это ограничение штатными средствами платформы. Логика заключается в разделении вычислений на две независимые агрегатные колонки.

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

Посмотрим на исправленный и рабочий пример составления текста запроса:


ТекстЗапроса="//{{ЗАПРОС(ПриходныеНакладные)
|Период с ДатаНачала по ДатаОкончания;
|Товар = Документ.ПриходнаяНакладная.Товар;
|ВидОперации = Документ.ПриходнаяНакладная.ВидОперации;
|Количество = Документ.ПриходнаяНакладная.Количество;
|Функция СуммаКоличествоПриход = Сумма(Количество) КОГДА (ВидОперации = 0);
|Функция СуммаКоличествоРасход = Сумма(Количество) КОГДА (ВидОперации = 1);
|Группировка Товар без групп;
|"//}}ЗАПРОС

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


Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

Пока Запрос.Группировка("Товар") = 1 Цикл
    // Вычисляем итоговое количество вручную
    ТвоеНужноеКоличество = Запрос.СуммаКоличествоПриход - Запрос.СуммаКоличествоРасход;
    
    // Передаем данные в печатную форму
    Таб.ВывестиСекцию("Строка");
КонецЦикла;

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

Решение 2: Оптимизация через использование регистров

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

Если задача заключается в получении остатков или оборотов товаров, правильнее использовать запросы к регистрам (например, регистр остатков товаров). Выясним причину, почему регистры удобнее: у регистра уже на уровне архитектуры заложено понятие "Приход" (знак плюс) и "Расход" (знак минус).

При обращении к регистрам переменная Количество будет автоматически возвращать правильный знак. Достаточно использовать функцию Оборот(Количество), и платформа сама учтет, что документ поступления прибавил товар, а документ возврата или списания — убавил. Это избавляет программиста от необходимости вручную проверять виды операций и рассчитывать две разные функции, а сам запрос отработает в разы быстрее.

Решение 3: Прямые SQL-запросы с компонентой 1С++

Если штатной производительности системы не хватает, или логика условий слишком громоздкая, разработчики применяют прямые SQL-запросы к базе данных через внешнюю компоненту 1С++. Разберем, как эта задача решается на уровне SQL.

В прямом SQL-запросе мы не скованы ограничениями платформы 7.7 и можем свободно использовать конструкцию CASE WHEN ... THEN ... ELSE ... END. Это позволяет получить нужную сумму с учетом знака операции прямо внутри базы данных, за один единственный проход таблиц:


SELECT
    Товар,
    SUM(CASE WHEN ВидОперации = 0 THEN Количество ELSE -Количество END) AS ИтогоКоличество
FROM
    Документ_ПриходнаяНакладная
GROUP BY
    Товар

Проанализируем этот метод: база данных (например, MS SQL Server) самостоятельно обрабатывает условие для каждой строки и сразу возвращает нам готовый результат в колонку ИтогоКоличество. Мы не создаем лишних промежуточных переменных и полностью избавляемся от математических операций в цикле платформы, что существенно ускоряет формирование сложных отчетов.

← На главную