Как исправить некорректную фильтрацию данных по сумме в отчете 1С v7.7?

Программист 1С v7.7 Управленческий учет Промышленность, строительство и АПК
← На главную

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

Первоначально пользователь сообщил о проблеме "не работает сортировка", однако дальнейшее уточнение показало, что речь идет не о сортировке (упорядочивании) данных, а именно о фильтрации, то есть отборе строк, сумма по которым превышает указанное значение.

Анализ текущей реализации фильтрации

Давайте проанализируем предоставленный код процедуры Сформировать_(), которая отвечает за формирование отчета. Основная проблема кроется в том, как и на каком уровне применяется условие фильтрации по сумме.

Ниже представлен проблемный участок кода, демонстрирующий вложенные циклы группировки запроса и блок, содержащий условие фильтрации:


// ...
Пока Запрос.Группировка(1) = 1 Цикл // Группировка по Отделу
    // ...
    Пока Запрос.Группировка(2) = 1 Цикл // Группировка по Документам
        // ...
        Пока Запрос.Группировка(3) = 1 Цикл // Группировка по Вотчет
            // ...
            Пока Запрос.Группировка(4) = 1 Цикл // Группировка по Объекту
                // ... инициализация переменных ...
                Пока Запрос.Группировка(5) = 1 Цикл // Группировка по ТекДок (документу)
                    // ... здесь определяется СуммаДог для текущего документа ...
                    Если Запрос.Документы.Вид() = "ДоговорНаПроектирование" Тогда
                        // ...
                        СуммаДог = ТабЗДоговоров.СуммаДог; // Или Запрос.СуммаДог
                        // ...
                    КонецЕсли;
                    Если Запрос.Документы.Вид() = "ДопСоглашение" Тогда
                        // ...
                        СуммаДог = ТабЗДопСоглашения.ОбщСуммаДог; // Или Запрос.Документы.Сумма
                        // ...
                    КонецЕсли;
                КонецЦикла; // Запрос.Группировка(5) завершилась
                
                // В этом месте применяется условие фильтрации
                Если ПустаяСтрока(Стоим) <> 1 Тогда
                    Если Число(СуммаДог) > Число(Стоим) Тогда // Проблемное условие
                        Номер = Номер + 1;
                        Таб.ВывестиСекцию("Данные");
                        _СуммаДог = _СуммаДог + СуммаДог;
                    КонецЕсли;    
                Иначе
                    Номер = Номер + 1;
                    Таб.ВывестиСекцию("Данные");
                    _СуммаДог = _СуммаДог + СуммаДог;
                КонецЕсли;
            КонецЦикла; // Запрос.Группировка(4)
            // ...
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

Выясняем причину некорректной работы

Проблема заключается в том, что условие Если Число(СуммаДог) > Число(Стоим) Тогда применяется не к агрегированной (общей) сумме по текущей группировке "Объект", а к переменной СуммаДог, значение которой было установлено в самом внутреннем цикле (по ТекДок). Это означает следующее:

  1. Запрос группирует данные по нескольким уровням: Отдел, Документы, Вотчет, Объект, ТекДок.
  2. Внутри цикла Пока Запрос.Группировка(5) = 1 Цикл переменная СуммаДог получает значение суммы для каждого отдельного документа (договора или доп. соглашения).
  3. После завершения этого внутреннего цикла (КонецЦикла; для Группировка(5)), переменная СуммаДог сохраняет значение суммы последнего обработанного документа в текущей группировке по "Объекту".
  4. Когда код доходит до условия Если Число(СуммаДог) > Число(Стоим) Тогда, он сравнивает значение Стоим не с общей суммой всех документов для данного объекта, а только с суммой этого последнего документа.

Таким образом, фильтр срабатывает некорректно, поскольку он не учитывает совокупную стоимость объекта, а ориентируется лишь на один из документов.

Предлагаемые решения для корректной фильтрации по сумме

Для того чтобы фильтр "Стоимость больше" работал правильно, нам необходимо сравнивать заданное значение (Стоим) с агрегированной суммой по каждому Объекту, а не с суммой отдельного документа. Рассмотрим два основных подхода к решению этой задачи.

Подход 1: Использование агрегатных функций запроса

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

Разберем по шагам, как модифицировать код:

  1. Убедимся, что в запросе присутствует функция, агрегирующая сумму по полю СуммаДог, например: |Функция ОбщСуммаДог = Приход(СуммаДог);. В вашем коде она уже есть.
  2. Переместим блок фильтрации таким образом, чтобы он применялся к значению Запрос.ОбщСуммаДог на уровне группировки по Объекту.

Посмотрим на пример модификации:


// ...
Пока Запрос.Группировка(1) = 1 Цикл // Группировка по Отделу
    // ...
    Пока Запрос.Группировка(2) = 1 Цикл // Группировка по Документам
        // ...
        Пока Запрос.Группировка(3) = 1 Цикл // Группировка по Вотчет
            // ...
            Пока Запрос.Группировка(4) = 1 Цикл // Группировка по Объекту
                // Здесь Запрос.Объект представляет текущий объект, а Запрос.ОбщСуммаДог
                // содержит сумму всех договоров и доп.соглашений для данного объекта в текущем периоде.

                // В этом месте Запрос.ОбщСуммаДог уже содержит общую сумму по текущему Объекту.
                // Переменные СуммаДог и ДогДатаС/По могут быть получены внутри цикла (5),
                // но для фильтрации нам нужна общая сумма по Объекту.

                Если ПустаяСтрока(Стоим) <> 1 Тогда
                    Если Число(Запрос.ОбщСуммаДог) > Число(Стоим) Тогда // Применяем фильтр к агрегированной сумме Объекта
                        Номер = Номер + 1;
                        Таб.ВывестиСекцию("Данные");
                        _СуммаДог = _СуммаДог + Запрос.ОбщСуммаДог; // Накапливаем общую сумму по отделу
                    КонецЕсли;
                Иначе
                    // Если условие на Стоим не задано, выводим данные без фильтрации по сумме
                    Номер = Номер + 1;
                    Таб.ВывестиСекцию("Данные");
                    _СуммаДог = _СуммаДог + Запрос.ОбщСуммаДог; // Накапливаем общую сумму по отделу
                КонецЕсли;

                // Для корректного отображения деталей документов (ТекДок)
                // вам может понадобиться дополнительный цикл по Запрос.Группировка(5)
                // внутри этого блока, если отчет должен детализировать каждый документ,
                // прошедший общую фильтрацию по объекту.
                // В противном случае, если отображается только сводная строка по объекту,
                // цикл (5) может быть излишним для вывода, но необходимым для получения
                // других деталей документов, которые не являются агрегированными (например, Прим).
                
                // Текущий код из примера сохраняет СуммаДог и другие параметры из последнего документа.
                // Возможно, вам потребуется изменить логику сбора этих данных или их отображение,
                // если теперь данные выводятся на уровне Объекта.
                // Например, Прим можно сделать агрегатной функцией в запросе (первые 50 символов всех примечаний),
                // или получить его из первого/последнего документа группы, если это допустимо.
                
            КонецЦикла; // Запрос.Группировка(4)
            // ...
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

В этом случае, переменную СуммаДог, определенную внутри цикла по ТекДок, больше не потребуется использовать для фильтрации на этом уровне. Важно также убедиться, что _СуммаДог (итоговая сумма по отделу) накапливается корректно, используя Запрос.ОбщСуммаДог, а не СуммаДог из внутреннего цикла, как показано в примере выше.

Подход 2: Накопление суммы в переменной перед фильтрацией (менее оптимально)

Если по какой-то причине (например, из-за специфики дальнейшей обработки данных внутри цикла ТекДок) невозможно использовать агрегатную функцию запроса напрямую, можно накопить сумму по Объекту в отдельной переменной внутри цикла Пока Запрос.Группировка(4) = 1 Цикл.

Посмотрим на пример такой модификации:


// ...
Пока Запрос.Группировка(4) = 1 Цикл // Группировка по Объекту
    ТекущаяСуммаПоОбъекту = 0; // Инициализируем переменную для накопления суммы по текущему объекту
    // ...
    Пока Запрос.Группировка(5) = 1 Цикл // Группировка по ТекДок (документу)
        // ... определяем СуммаДог для текущего документа ...
        Если Запрос.Документы.Вид() = "ДоговорНаПроектирование" Тогда
            Нс=0;
            Рез=ТабЗДоговоров.НайтиЗначение(Запрос._Объект,нс,"_Объект");
            Если Рез=1 Тогда
                ТабЗДоговоров.ПолучитьСтрокуПоНомеру(нс);
                СуммаДог = ТабЗДоговоров.СуммаДог;
                // ... другие параметры ...
            Иначе
                СуммаДог = Запрос.СуммаДог; // Если не найдено в ТабЗДоговоров, берем из запроса
                // ... другие параметры ...
            КонецЕсли;
        КонецЕсли;  
        Если Запрос.Документы.Вид() = "ДопСоглашение" Тогда
            Нс=0;
            Рез=ТабЗДопСоглашения.НайтиЗначение(Запрос._Объект,нс,"_Объект");
            Если Рез=1 Тогда
                ТабЗДопСоглашения.ПолучитьСтрокуПоНомеру(нс);
                СуммаДог = ТабЗДопСоглашения.ОбщСуммаДог; // Здесь, возможно, нужна СуммаДопСогл, а не ОбщСуммаДог
                // ... другие параметры ...
            Иначе
                // Если не найдено в ТабЗДопСоглашения, берем из запроса
                // Здесь, вероятно, нужна Запрос.СуммаДопСогл, если это для ДопСоглашения
                // Или же Запрос.СуммаДог, если в запросе ДопСоглашение.СуммаДог
                СуммаДог = Запрос.СуммаДог; // Или Запрос.СуммаДопСогл
                // ... другие параметры ...
            КонецЕсли;
        КонецЕсли; 
        
        // Накапливаем сумму для текущего Объекта
        ТекущаяСуммаПоОбъекту = ТекущаяСуммаПоОбъекту + СуммаДог; 
    КонецЦикла; // Запрос.Группировка(5)
    
    // Здесь ТекущаяСуммаПоОбъекту содержит сумму всех документов для данного объекта
    Если ПустаяСтрока(Стоим) <> 1 Тогда
        Если Число(ТекущаяСуммаПоОбъекту) > Число(Стоим) Тогда
            Номер = Номер + 1;
            Таб.ВывестиСекцию("Данные");
            _СуммаДог = _СуммаДог + ТекущаяСуммаПоОбъекту; // Накапливаем общую сумму по отделу
        КонецЕсли;
    Иначе
        Номер = Номер + 1;
        Таб.ВывестиСекцию("Данные");
        _СуммаДог = _СуммаДог + ТекущаяСуммаПоОбъекту; // Накапливаем общую сумму по отделу
    КонецЕсли;
КонецЦикла; // Запрос.Группировка(4)

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

Дополнительные рекомендации по оптимизации и работе с отчетами в 1С v7.7

При работе с отчетами и запросами в 1С v7.7 важно учитывать не только корректность логики, но и производительность. Рассмотрим несколько общих рекомендаций:

  1. Сортировка данных: Для получения изначально отсортированных данных используйте оператор Упорядочить По (ORDER BY) непосредственно в тексте запроса. Например, добавьте |Упорядочить По _Объект, ДатаНачПроект; после всех группировок. Это позволит базе данных отдать уже упорядоченные данные, снижая нагрузку на клиентскую часть. Для сортировки по убыванию используйте знак минус (например, |Упорядочить По ДатаОконПроект-;).
  2. Клиентская сортировка: Если данные уже выгружены в объект ТаблицаЗначений, можно использовать метод ТЗ.Сортировать(). Этот метод выполняет сортировку уже загруженных в оперативную память данных. Пример: Тз.Сортировать("_Объект,ДатаНачПроект+").
  3. Фильтрация данных (Условие): Для фильтрации данных на уровне запроса используйте оператор Условие. Это позволяет отсекать ненужные данные до их выгрузки в оперативную память. Пример: |Условие (Стадия.Код <> Строка(1));.
  4. Двойное выполнение запроса: Избегайте повторного вызова Запрос.Выполнить(). В процедурах ЗаполнитьТЗДог и ЗаполнитьТЗДоп запрос выполняется дважды, что является избыточным. Достаточно выполнить его один раз и затем проверить результат.
  5. Сложность запросов и вложенные циклы: Старайтесь максимально использовать возможности запросов 1С v7.7 для получения уже агрегированных и отфильтрованных данных. Большое количество группировок и вложенные циклы обработки данных на стороне клиента могут значительно снижать производительность.
  6. Использование временных таблиц: Для сложных многошаговых расчетов в запросах 1С v7.7 можно использовать временные таблицы (например, через оператор ИТОГИ ПО с последующим созданием нового запроса или объединением результатов нескольких запросов).
  7. Кэширование свойств объектов: Многократное обращение к свойствам объектов (например, ТекДок.Объект.Архив) внутри циклов может быть медленным. Если значение свойства не меняется внутри цикла, его лучше получить один раз до начала цикла и использовать кэшированное значение.
  8. Работа с датами: При работе с датами в 1С v7.7 важно учитывать формат и производить сравнения корректно. Используйте функции Дата() или переменные типа "Дата" вместо строковых представлений дат (например, Дата1='01.01.10').

Применяя эти принципы, вы сможете создавать более производительные и корректно работающие отчеты в 1С v7.7.

← На главную