При разработке отчетов и обработок во встроенном языке 1С разработчики часто сталкиваются с ситуацией, когда итоговые суммы и количество документов в выборке существенно превышают ожидаемые значения. Рассмотрим основные причины возникновения таких расхождений, разберем типичные архитектурные ошибки при формировании выборок и выясним, как правильно объединить вычисление итогов и детализацию в единый оптимальный запрос.
Проанализируем распространенные источники ошибок, которые приводят к искажению числовых результатов:
Регистратор.НаОсновании.Ссылка в условиях или полях выборки вызывают скрытые соединения (LEFT JOIN) сразу со всеми возможными таблицами документов составного типа, что может порождать дублирование записей и замедлять работу.ГДЕ. Наложение условий на измерения в секции ГДЕ вместо передачи их в параметры виртуальной таблицы (например, таблицы оборотов) заставляет платформу считывать все записи регистра за период, а не только целевые.Посмотрим на пример кода, в котором для каждого контрагента в цикле выполняются отдельные запросы подсчета итогов и выборки документов:
// Неоптимальный подход с запросами в цикле
ВыбКон = ЗапКон.Выполнить().Выбрать();
Пока ВыбКон.Следующий() Цикл
ЭлементыФормы.ПолеОтчёт.ДобавитьСтроку();
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.Дропшипер = ВыбКон.Кон;
// Запрос итогов без учета флагов оплаты
ЗапИтог = Новый Запрос;
ЗапИтог.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РасходнойДокумент.Ссылка) КАК Док,
| СУММА(РасходнойДокумент.СумаДокумента) КАК СумаДокумента,
| СУММА(РасходнойДокумент.СумаГонорара) КАК СумаГонорара
|ИЗ
| Документ.РасходнойДокумент КАК РасходнойДокумент
|ГДЕ
| РасходнойДокумент.Контрагент = &Кон
| И РасходнойДокумент.Дата >= &ДатНач
| И РасходнойДокумент.Дата <= &ДатКон
| И РасходнойДокумент.Проведен = ИСТИНА";
// Установка параметров и выполнение...
КонецЦикла;
В данном примере запрос итогов выбирает вообще все проведенные документы контрагента за период, в то время как расшифровка ниже по коду отбирает документы с учетом условий Гонорар = ИСТИНА и проверки оплаты через ДокументПКО. В результате в шапке группировки отображаются одни цифры, а в детальных строках — совершенно другие.
Разберем по шагам, как избавиться от запросов в цикле и получить корректные сводные и детальные данные одновременно. Для этого воспользуемся секцией ИТОГИ ... ПО, объединив агрегатные функции и детальные поля в одном запросе.
Процедура СформироватьОтчет()
ДатНач = НачалоДня(ЭлементыФормы.НачалоПериода.Значение);
ДатКон = КонецДня(ЭлементыФормы.КонецПериода.Значение);
Контрик = ЭлементыФормы.Дропшипер.Значение;
ЭлементыФормы.ПолеОтчёт.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасходнойДокумент.Контрагент КАК Контрагент,
| РасходнойДокумент.Ссылка КАК Док,
| РасходнойДокумент.Дата КАК Дата,
| РасходнойДокумент.СумаДокумента КАК СумаДокумента,
| РасходнойДокумент.СумаГонорара КАК СумаГонорара,
| РасходнойДокумент.НомерЕН КАК НомерЕН,
| РасходнойДокумент.ИнфаКонтр КАК ИнфаКонтр,
| РасходнойДокумент.Гонорар КАК Гонорар
|ИЗ
| Документ.РасходнойДокумент КАК РасходнойДокумент
|ГДЕ
| РасходнойДокумент.Контрагент В ИЕРАРХИИ (&Контр)
| И РасходнойДокумент.Дата МЕЖДУ &ДатНач И &ДатКон
| И РасходнойДокумент.Проведен
| И (&ВсеПоОплате ИЛИ РасходнойДокумент.ДокументПКО <> ЗНАЧЕНИЕ(Документ.ПриходнойКассовыйОрдер.ПустаяСсылка))
| И РасходнойДокумент.Гонорар = &Гонорар
|ИТОГИ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Док),
| СУММА(СумаДокумента),
| СУММА(СумаГонорара)
|ПО
| Контрагент";
РодительПоУмолчанию = Справочники.Контрагенты.НайтиПоКоду("000000005");
Запрос.УстановитьПараметр("Контр", ?(Контрик.Пустая(), РодительПоУмолчанию, Контрик));
Запрос.УстановитьПараметр("ДатНач", ДатНач);
Запрос.УстановитьПараметр("ДатКон", ДатКон);
Запрос.УстановитьПараметр("ВсеПоОплате", НЕ ЭлементыФормы.ТолькоОплаченные.Значение);
Запрос.УстановитьПараметр("Гонорар", ЭлементыФормы.Гонорар.Значение);
РезультатЗапроса = Запрос.Выполнить();
// Обход группировок (итоги по контрагентам)
ВыборкаГруппировка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГруппировка.Следующий() Цикл
ЭлементыФормы.ПолеОтчёт.ДобавитьСтроку();
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.Дропшипер = ВыборкаГруппировка.Контрагент;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.РасходнойДокумент = ВыборкаГруппировка.Док;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.СумаДокумента = ВыборкаГруппировка.СумаДокумента;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.Гонорар = ВыборкаГруппировка.СумаГонорара;
// Обход детальных записей внутри текущей группировки
ВыборкаДетали = ВыборкаГруппировка.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
ЭлементыФормы.ПолеОтчёт.ДобавитьСтроку();
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.РасходнойДокумент = ВыборкаДетали.Док;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.ДатаДокумента = ВыборкаДетали.Дата;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.СумаДокумента = ВыборкаДетали.СумаДокумента;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.НомерТТН = ВыборкаДетали.НомерЕН;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.ГонорарГалочка = ВыборкаДетали.Гонорар;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.Гонорар = ВыборкаДетали.СумаГонорара;
ЭлементыФормы.ПолеОтчёт.ТекущаяСтрока.Покупатель = ВыборкаДетали.ИнфаКонтр;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Рассмотрим правила, которые позволяют избежать логических и платформенных ошибок при работе с запросами:
ЗНАЧЕНИЕ: Для проверки пустых ссылок, перечислений и предопределенных элементов используйте конструкцию ЗНАЧЕНИЕ(...) непосредственно в тексте запроса вместо передачи пустых ссылок через параметры из встроенного языка.И НЕ Документ.ПометкаУдаления является избыточным, если в запросе уже проверяется условие И Документ.Проведен, так как помеченный на удаление документ не может быть проведен.МЕЖДУ в сочетании с обработкой границ через НачалоДня() и КонецДня() (или функцию запроса КОНЕЦПЕРИОДА(&ДатКон, ДЕНЬ)).