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