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