Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с ситуацией, когда правильно написанный запрос неожиданно возвращает дублирующиеся строки при выводе результатов. Выгруженные данные кажутся задвоенными, хотя в самой базе данных записи уникальны. Особенно часто эта проблема возникает при использовании секции ИТОГИ.
В этой статье мы подробно разберем, почему возникают кажущиеся «дубли» при обработке запросов с итогами, выясним физический смысл этого поведения платформы и рассмотрим профессиональные способы правильного обхода иерархических выборок.
Давайте проанализируем классическую ситуацию. Разработчик пишет запрос к регистру накопления с расчетом итогов по определенному измерению (например, Специфика, сокращенно СП):
ВЫБРАТЬ
БПФПоОбязательствамНПОбороты.Специфика КАК СП,
БПФПоОбязательствамНПОбороты.СуммаЯнварьПриход КАК Январь
ИЗ
РегистрНакопления.ПланПоБюджетуФинансированияПоОбязательствамНП.Обороты(...) КАК БПФПоОбязательствамНПОбороты
ИТОГИ
СУММА(Январь)
ПО
СП
Затем обработка результата выполняется самым простым и очевидным циклом:
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
Сообщить("Специфика: " + РезультатЗапроса.СП + " | Январь: " + РезультатЗапроса.Январь);
КонецЦикла;
При таком подходе в окно сообщений будут выведены повторяющиеся строки для одних и тех же значений поля СП. Почему так происходит?
Когда в тексте запроса присутствует секция ИТОГИ, СУБД возвращает не плоскую таблицу, а сложную древовидную структуру данных. В этой структуре присутствуют записи двух принципиально разных типов:
ИТОГИ полям.Если мы вызываем метод Выбрать() без параметров, платформа 1С выполняет так называемый прямой (линейный) обход. При прямом обходе система последовательно выдает абсолютно все строки результирующего набора данных — сначала строку итогов по группировке, а затем сразу за ней все детальные строки, которые в эту группировку входят. Поскольку в строке итогов поле группировки СП заполнено, а в детальных строках оно имеет точно такое же значение, в плоском цикле это выглядит как дублирование строк.
Давайте рассмотрим три эффективных решения этой проблемы в зависимости от ваших задач.
Если вам действительно необходимы итоги в отчете или обработке, правильным подходом будет использование иерархического обхода. Для этого при вызове метода Выбрать() необходимо явно передать параметр ОбходРезультатаЗапроса.ПоГруппировкам.
Давайте разберем по шагам, как построить логику обхода с использованием вложенных циклов:
// Шаг 1. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
// Шаг 2. Получаем выборку верхнего уровня (только итоги по группировкам)
ВыборкаГруппировка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГруппировка.Следующий() Цикл
// Здесь мы позиционируемся строго на итоговой строке группировки
Сообщить("ИТОГ по спецификации " + ВыборкаГруппировка.СП + ": " + ВыборкаГруппировка.Январь);
// Шаг 3. Получаем подчиненную выборку детальных записей для ТЕКУЩЕЙ группировки
ВыборкаДетали = ВыборкаГруппировка.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
// Здесь мы обрабатываем детальные записи, относящиеся к текущей группировке
Сообщить(" Детальная запись. Организация: " + ВыборкаДетали.Организация + ", Сумма: " + ВыборкаДетали.Январь);
КонецЦикла;
КонецЦикла;
При таком подходе данные четко разделены: на внешнем уровне цикла мы получаем агрегированные суммы, а на внутреннем — расшифровку этих сумм без какого-либо дублирования.
Бывают ситуации, когда по архитектурным соображениям вам необходимо использовать именно прямой (линейный) обход в один цикл, но при этом требуется обрабатывать только детальные строки, игнорируя строки итогов (или наоборот). Для реализации этого сценария платформа предлагает использовать специальные методы объекта ВыборкаИзРезультатаЗапроса.
Проанализируем пример фильтрации с использованием системного перечисления ТипЗаписиЗапроса:
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.Прямой);
Пока Выборка.Следующий() Цикл
// Проверяем тип текущей строки выборки
Если Выборка.ТипЗаписи() = ТипЗаписиЗапроса.ДетальнаяЗапись Тогда
// Сюда попадают только физические строки данных без строк итогов
Сообщить("Детальная строка: " + Выборка.СП + " - " + Выборка.Январь);
ИначеЕсли Выборка.ТипЗаписи() = ТипЗаписиЗапроса.ИтогПоГруппировке Тогда
// Здесь обрабатываются только строки промежуточных итогов
Сообщить("Итоговая строка по группировке: " + Выборка.Группировка() + " со значением: " + Выборка.СП);
КонецЕсли;
КонецЦикла;
Использование свойств ТипЗаписи(), Группировка() и Уровень() позволяет гибко управлять логикой обработки плоского списка данных, отсекая ненужные уровни иерархии прямо «на лету».
Если ваша конечная цель — вывести иерархическую структуру данных на форму (например, в элемент управления «Поле дерева значений»), вам совсем не обязательно вручную писать сложные циклы обхода.
Платформа 1С умеет автоматически преобразовывать иерархические результаты запроса в системный объект ДеревоЗначений. Для этого достаточно использовать метод Выгрузить() с указанием типа обхода:
РезультатЗапроса = Запрос.Выполнить();
// Метод автоматически создаст дерево, где ветвями будут группировки ИТОГОВ,
// а листьями — детальные записи, подчиненные соответствующим ветвям.
ДеревоДанных = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
Этот способ существенно экономит время разработки, минимизирует количество строк кода и работает на уровне платформы крайне быстро.
Рассматривая проблему дубликатов, крайне важно задать себе вопрос: «А действительно ли мне нужны итоги в этом запросе?».
Если в коде обработки вы просто выводите детальные записи на экран или формируете плоскую таблицу и никак не используете агрегированные суммы по группировкам, то присутствие секции ИТОГИ в тексте запроса является серьезной ошибкой.
Почему это важно для производительности:
Если итоги не используются, просто удалите секцию ИТОГИ и секцию УПОРЯДОЧИТЬ ПО (если сортировка также не требуется для бизнес-логики) из вашего запроса. Это мгновенно решит проблему с «дубликатами» при обычном обходе и повысит скорость работы системы.