Почему запрос 1С возвращает дубликаты при использовании ИТОГИ и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с ситуацией, когда правильно написанный запрос неожиданно возвращает дублирующиеся строки при выводе результатов. Выгруженные данные кажутся задвоенными, хотя в самой базе данных записи уникальны. Особенно часто эта проблема возникает при использовании секции ИТОГИ.

В этой статье мы подробно разберем, почему возникают кажущиеся «дубли» при обработке запросов с итогами, выясним физический смысл этого поведения платформы и рассмотрим профессиональные способы правильного обхода иерархических выборок.

Выясняем причину: почему появляются «дубликаты»?

Давайте проанализируем классическую ситуацию. Разработчик пишет запрос к регистру накопления с расчетом итогов по определенному измерению (например, Специфика, сокращенно СП):


ВЫБРАТЬ
    БПФПоОбязательствамНПОбороты.Специфика КАК СП,
    БПФПоОбязательствамНПОбороты.СуммаЯнварьПриход КАК Январь
ИЗ
    РегистрНакопления.ПланПоБюджетуФинансированияПоОбязательствамНП.Обороты(...) КАК БПФПоОбязательствамНПОбороты
ИТОГИ
    СУММА(Январь)
ПО
    СП

Затем обработка результата выполняется самым простым и очевидным циклом:


РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
    Сообщить("Специфика: " + РезультатЗапроса.СП + " | Январь: " + РезультатЗапроса.Январь);
КонецЦикла;

При таком подходе в окно сообщений будут выведены повторяющиеся строки для одних и тех же значений поля СП. Почему так происходит?

Когда в тексте запроса присутствует секция ИТОГИ, СУБД возвращает не плоскую таблицу, а сложную древовидную структуру данных. В этой структуре присутствуют записи двух принципиально разных типов:

  1. Детальные записи — это исходные строки данных из физических таблиц (например, движения по регистрам).
  2. Группировочные записи (итоги) — это строки, содержащие агрегированные, суммированные данные по указанным в секции ИТОГИ полям.

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

Давайте рассмотрим три эффективных решения этой проблемы в зависимости от ваших задач.

Способ 1: Многоуровневый обход по группировкам (вложенные циклы)

Если вам действительно необходимы итоги в отчете или обработке, правильным подходом будет использование иерархического обхода. Для этого при вызове метода Выбрать() необходимо явно передать параметр ОбходРезультатаЗапроса.ПоГруппировкам.

Давайте разберем по шагам, как построить логику обхода с использованием вложенных циклов:


// Шаг 1. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();

// Шаг 2. Получаем выборку верхнего уровня (только итоги по группировкам)
ВыборкаГруппировка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаГруппировка.Следующий() Цикл
    // Здесь мы позиционируемся строго на итоговой строке группировки
    Сообщить("ИТОГ по спецификации " + ВыборкаГруппировка.СП + ": " + ВыборкаГруппировка.Январь);
    
    // Шаг 3. Получаем подчиненную выборку детальных записей для ТЕКУЩЕЙ группировки
    ВыборкаДетали = ВыборкаГруппировка.Выбрать();
    
    Пока ВыборкаДетали.Следующий() Цикл
        // Здесь мы обрабатываем детальные записи, относящиеся к текущей группировке
        Сообщить("    Детальная запись. Организация: " + ВыборкаДетали.Организация + ", Сумма: " + ВыборкаДетали.Январь);
    КонецЦикла;
КонецЦикла;

При таком подходе данные четко разделены: на внешнем уровне цикла мы получаем агрегированные суммы, а на внутреннем — расшифровку этих сумм без какого-либо дублирования.

Способ 2: Фильтрация записей с помощью ТипЗаписи()

Бывают ситуации, когда по архитектурным соображениям вам необходимо использовать именно прямой (линейный) обход в один цикл, но при этом требуется обрабатывать только детальные строки, игнорируя строки итогов (или наоборот). Для реализации этого сценария платформа предлагает использовать специальные методы объекта ВыборкаИзРезультатаЗапроса.

Проанализируем пример фильтрации с использованием системного перечисления ТипЗаписиЗапроса:


РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.Прямой);

Пока Выборка.Следующий() Цикл
    // Проверяем тип текущей строки выборки
    Если Выборка.ТипЗаписи() = ТипЗаписиЗапроса.ДетальнаяЗапись Тогда
        // Сюда попадают только физические строки данных без строк итогов
        Сообщить("Детальная строка: " + Выборка.СП + " - " + Выборка.Январь);
    ИначеЕсли Выборка.ТипЗаписи() = ТипЗаписиЗапроса.ИтогПоГруппировке Тогда
        // Здесь обрабатываются только строки промежуточных итогов
        Сообщить("Итоговая строка по группировке: " + Выборка.Группировка() + " со значением: " + Выборка.СП);
    КонецЕсли;
КонецЦикла;

Использование свойств ТипЗаписи(), Группировка() и Уровень() позволяет гибко управлять логикой обработки плоского списка данных, отсекая ненужные уровни иерархии прямо «на лету».

Способ 3: Автоматическая выгрузка в Дерево Значений

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

Платформа 1С умеет автоматически преобразовывать иерархические результаты запроса в системный объект ДеревоЗначений. Для этого достаточно использовать метод Выгрузить() с указанием типа обхода:


РезультатЗапроса = Запрос.Выполнить();

// Метод автоматически создаст дерево, где ветвями будут группировки ИТОГОВ,
// а листьями — детальные записи, подчиненные соответствующим ветвям.
ДеревоДанных = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

Этот способ существенно экономит время разработки, минимизирует количество строк кода и работает на уровне платформы крайне быстро.

Важный совет по оптимизации: нужны ли ИТОГИ вообще?

Рассматривая проблему дубликатов, крайне важно задать себе вопрос: «А действительно ли мне нужны итоги в этом запросе?».

Если в коде обработки вы просто выводите детальные записи на экран или формируете плоскую таблицу и никак не используете агрегированные суммы по группировкам, то присутствие секции ИТОГИ в тексте запроса является серьезной ошибкой.

Почему это важно для производительности:

Если итоги не используются, просто удалите секцию ИТОГИ и секцию УПОРЯДОЧИТЬ ПО (если сортировка также не требуется для бизнес-логики) из вашего запроса. Это мгновенно решит проблему с «дубликатами» при обычном обходе и повысит скорость работы системы.

← На главную