Как в 1С 7.7 посчитать количество записей нижележащих группировок в запросе?

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

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

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

Почему встроенная функция Счётчик() не подходит?

Агрегатная функция встроенного языка запросов Счётчик() всегда считает количество атомарных физических записей источника данных (строк движений регистров или документов), вошедших в выборку. При сворачивании на верхних уровнях иерархии эти значения автоматически суммируются. Конструкция COUNT(DISTINCT) во встроенном языке запросов 1С 7.7 отсутствует, поэтому получить количество узлов следующего уровня группировки внутри самого текста запроса невозможно.

Проанализируем практические варианты решения этой задачи при выводе отчета.

Способ 1. Запись количества в выведенную секцию через метод Таб.Область()

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

Рассмотрим пример кода:


Пока Запрос.Группировка(1) = 1 Цикл
    Таб.ВывестиСекцию("Группировка1");
    ТекСтрока = Таб.ВысотаТаблицы();
    Сч = 0;
    
    Пока Запрос.Группировка(2) = 1 Цикл
        Таб.ВывестиСекцию("Группировка2");
        Сч = Сч + 1;
    КонецЦикла;
    
    // Записываем полученное количество в нужную колонку родительской секции
    Таб.Область(ТекСтрока, 3, ТекСтрока, 3).Текст = Строка(Сч);
КонецЦикла;

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

Способ 2. Двойной обход с позиционированием через Запрос.Получить()

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

Разберем по шагам логику этого алгоритма:

  1. Выполняем обход родительской группировки.
  2. Во внутреннем цикле подсчитываем количество элементов подчиненного уровня без вывода секций в таблицу.
  3. Выводим родительскую секцию, куда передаем вычисленное значение счетчика.
  4. Позиционируем выборку запроса обратно на текущий родительский элемент с помощью метода Запрос.Получить().
  5. Выполняем повторный обход подчиненной группировки и выводим детальные строки.

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


Пока Запрос.Группировка(1) = 1 Цикл
    Сч = 0;
    Пока Запрос.Группировка(2) = 1 Цикл
        Сч = Сч + 1;
    КонецЦикла;
    
    // Передаем переменную Сч в параметры секции макета
    Таб.ВывестиСекцию("Группировка1");
    
    // Возвращаем курсор на текущую ветку группировки
    Запрос.Получить(Запрос.Номенклатура);
    
    Пока Запрос.Группировка(2) = 1 Цикл
        Таб.ВывестиСекцию("Группировка2");
    КонецЦикла;
КонецЦикла;

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

Способ 3. Выгрузка в ТаблицуЗначений и использование метода Свернуть()

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

Рассмотрим последовательность действий:

  1. Выгружаем результат запроса в объект ТаблицаЗначений с помощью команды Запрос.Выгрузить(ТЗ, 0, 0).
  2. Создаем копию таблицы и сворачиваем ее по уникальным связкам измерений (например, ТЗКопия.Свернуть("Номенклатура,Партия", "")).
  3. Добавляем числовую колонку Счетчик, заполняем ее единицами и сворачиваем таблицу повторно: ТЗКопия.Свернуть("Номенклатура", "Счетчик").
  4. В результате колонка Счетчик содержит точное количество подчиненных партий для каждого товара.

Посмотрим на реализацию в коде:


ТЗ = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТЗ, 0, 0);

ТЗПодсчет = СоздатьОбъект("ТаблицаЗначений");
ТЗ.Выгрузить(ТЗПодсчет);

// Оставляем только уникальные комбинации Номенклатура-Партия
ТЗПодсчет.Свернуть("Номенклатура, Партия", "");
ТЗПодсчет.НоваяКолонка("КоличествоПартий", "Число");
ТЗПодсчет.Заполнить(1, 1, ТЗПодсчет.КоличествоСтрок(), "КоличествоПартий");

// Получаем итоговое количество партий на каждую номенклатуру
ТЗПодсчет.Свернуть("Номенклатура", "КоличествоПартий");

Способ 4. Разделение на параллельные запросы

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

  1. Первый запрос: формирует агрегированные данные только по верхним уровням (Номенклатура, Партия). Количество вхождений второй группировки определяет число подчиненных записей.
  2. Второй запрос: содержит полную детализацию (Номенклатура, Партия, Адрес) и используется для вывода строк нижнего уровня.

Для большинства стандартных отчетов оптимальным по простоте и скорости работы остается Способ 1 с использованием метода Таб.Область().

← На главную