Как сформировать отчет по двум запросам параллельно в 1С 7.7?

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

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

Анализ типичных ошибок при выводе данных двух запросов

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


// Вывод первого запроса
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");
Таб.ВывестиСекцию("Заголовок");
// ... обход группировок первого запроса ...

// Вывод второго запроса
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");
Таб.ВывестиСекцию("Заголовок1");
// ... обход группировок второго запроса ...
Таб.Показать("Сформировать", "");

В этом фрагменте переменная Таб инициализируется заново перед обработкой второго запроса: СоздатьОбъект("Таблица") полностью уничтожает предыдущий экземпляр печатного документа вместе со всеми ранее выведенными строками. В итоге на экран выводится только результат второго запроса.

Кроме того, язык запросов 1С 7.7 имеет важные синтаксические ограничения:

  1. В секции Условие нельзя использовать логический оператор ИЛИ. Конструкция вида Условие(Расчет = ВыбРасчет ИЛИ Расчет = ВыбРасчет1); вызовет ошибку синтаксиса. Для множественного отбора необходимо использовать проверку на вхождение в список: Условие(Расчет В ВыбРасчеты);, предварительно передав в запрос объект СписокЗначений.
  2. У объекта Запрос нет свойства КоличествоСтрок. Данные запроса обходятся последовательно через вызов метода Группировка() либо выгружаются целиком в коллекцию методом Выгрузить().

Способ 1. Получение данных в один запрос с условными функциями (рекомендуемый)

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

Разберем пример реализации:


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

Этот подход исключает дублирование обходов и гарантирует, что данные по двум колонкам будут строго синхронизированы по документам.

Способ 2. Синхронизация двух запросов через Таблицу Значений

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

Посмотрим на пошаговый алгоритм:

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

Процедура Сформировать()
    Перем Запрос1, Запрос2, ТекстЗапроса, Таб;
    Перем ТЗ1, ТЗ2, ВсегоСтрок, й;
    
    // Выполнение первого запроса
    Запрос1 = СоздатьОбъект("Запрос");
    ТекстЗапроса = "
    |Период с ВыбНачПериода по ВыбКонПериода;
    |ДатаДок = Документ.НачисленияУдержанияСписком.ДатаДок;
    |НомерДок = Документ.НачисленияУдержанияСписком.НомерДок;
    |Расчет = Документ.НачисленияУдержанияСписком.Расчет;
    |Величина = Документ.НачисленияУдержанияСписком.Величина;
    |Функция Итого1 = Сумма(Число(Величина));
    |Группировка НомерДок без упорядочивания;
    |Условие(Расчет = ВыбРасчет);
    |";
    Если Запрос1.Выполнить(ТекстЗапроса) = 0 Тогда
        Возврат;
    КонецЕсли;
    
    ТЗ1 = СоздатьОбъект("ТаблицаЗначений");
    Запрос1.Выгрузить(ТЗ1);
    
    // Выполнение второго запроса
    Запрос2 = СоздатьОбъект("Запрос");
    ТекстЗапроса = "
    |Период с ВыбНачПериода по ВыбКонПериода;
    |ДатаДок = Документ.НачисленияУдержанияСписком.ДатаДок;
    |НомерДок = Документ.НачисленияУдержанияСписком.НомерДок;
    |Расчет = Документ.НачисленияУдержанияСписком.Расчет;
    |Величина = Документ.НачисленияУдержанияСписком.Величина;
    |Функция Итого2 = Сумма(Число(Величина));
    |Группировка НомерДок без упорядочивания;
    |Условие(Расчет = ВыбРасчет1);
    |";
    Если Запрос2.Выполнить(ТекстЗапроса) = 0 Тогда
        Возврат;
    КонецЕсли;
    
    ТЗ2 = СоздатьОбъект("ТаблицаЗначений");
    Запрос2.Выгрузить(ТЗ2);
    
    // Инициализация печатной формы
    Таб = СоздатьОбъект("Таблица");
    Таб.ИсходнаяТаблица("Сформировать");
    Таб.ВывестиСекцию("Заголовок");
    
    ВсегоСтрок = Макс(ТЗ1.КоличествоСтрок(), ТЗ2.КоличествоСтрок());
    
    Для й = 1 По ВсегоСтрок Цикл
        // Считываем значения для левой колонки отчета
        Если й <= ТЗ1.КоличествоСтрок() Тогда
            ТЗ1.ПолучитьСтрокуПоНомеру(й);
            НомерДок1 = ТЗ1.НомерДок;
            Сумма1    = ТЗ1.Итого1;
        Иначе
            НомерДок1 = "";
            Сумма1    = 0;
        КонецЕсли;
        
        // Считываем значения для правой колонки отчета
        Если й <= ТЗ2.КоличествоСтрок() Тогда
            ТЗ2.ПолучитьСтрокуПоНомеру(й);
            НомерДок2 = ТЗ2.НомерДок;
            Сумма2    = ТЗ2.Итого2;
        Иначе
            НомерДок2 = "";
            Сумма2    = 0;
        КонецЕсли;
        
        // Выводим строку, в которой одновременно присутствуют переменные левой и правой колонок
        Таб.ВывестиСекцию("Строка");
    КонецЦикла;
    
    // Итоги по колонкам рассчитываются стандартным методом таблицы значений
    ИтогСумма1 = ТЗ1.Итог("Итого1");
    ИтогСумма2 = ТЗ2.Итог("Итого2");
    Таб.ВывестиСекцию("Итого");
    
    Таб.ТолькоПросмотр(1);
    Таб.Показать("Сформировать", "");
КонецПроцедуры

Способ 3. Параллельный вывод с помощью метода ПрисоединитьСекцию

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

Для реализации этого метода макет размечается пересекающимися областями:

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

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

← На главную