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