Работа с запросами в 1С:Предприятие 8.3 — основа аналитики и обработки данных. Но что делать, когда одного запроса недостаточно? Второй запрос может понадобиться для уточнения результатов, объединения данных из разных источников или оптимизации сложных вычислений. Однако его неправильное использование часто приводит к ошибкам времени выполнения, избыточной нагрузке на базу или некорректным результатам.
В этой статье разберём три ключевых сценария, когда второй запрос необходим: последовательное выполнение, вложенные запросы и объединение результатов. Особое внимание уделим типичным ошибкам (например, Неопределённое поведение при соединении временных таблиц) и способам их избежать. Также покажем, как использовать менеджер временных таблиц для ускорения работы с большими наборами данных — техника, которую редко освещают в стандартной документации.
Материал ориентирован на разработчиков 1С уровня "средний-продвинутый", но будет полезен и новичкам благодаря пошаговым примерам. Все кодовые блоки протестированы на платформе 1С:Предприятие 8.3.22 (актуально для конфигураций УТ 11.5, БП 3.0, ЗУП 3.1).
1. Когда нужен второй запрос: 3 основных случая
Прежде чем писать код, определите цель второго запроса. Его использование оправдано только в трёх ситуациях:
- 🔄 Последовательная обработка: когда результат первого запроса нужно дополнительно отфильтровать или преобразовать. Например, вы получили список документов, а затем хотите уточнить остатки по каждому из них.
- 🔗 Объединение данных из разных источников: если информация разбросана по нескольким справочникам или регистрам (например, данные о клиенте из справочника
Контрагентыи история его заказов из регистраПродажи). - ⚡ Оптимизация производительности: разбиение одного сложного запроса на два простых иногда ускоряет выполнение за счёт уменьшения временных таблиц.
В остальных случаях второй запрос — это антипаттерн, который усложняет код и замедляет работу. Например, если вы можете получить все нужные данные за один проход, не делите запрос на части.
⚠️ Внимание: В конфигурациях с управляемыми формами (например, УТ 11 или ERP 2) второй запрос в обработчике события формы может вызватьОшибку блокировки данных. В таких случаях используйтеОтложенное выполнениечерезФоновые задания.
| Сценарий | Пример задачи | Оптимальный метод |
|---|---|---|
| Последовательная фильтрация | Получить список клиентов, затем уточнить их долги | Временная таблица + второй запрос с ГДЕ |
| Объединение данных | Связать справочник номенклатуры с регистром цен | Запрос с ОБЪЕДИНИТЬ или СОЕДИНИТЬ |
| Оптимизация | Разбить сложный отчёт на этапы | Менеджер временных таблиц |
2. Техника выполнения: от простого к сложному
Рассмотрим три способа организации второго запроса — от элементарного до профессионального. Начнём с базового подхода, который подходит для 80% задач.
2.1. Последовательные запросы (для начинающих)
Самый очевидный метод: выполнить первый запрос, сохранить его результат во временную таблицу, затем использовать её во втором запросе. Пример:
// 1. Первый запрос: получаем список документов
Запрос1 = Новый Запрос;
Запрос1.Текст =
"ВЫБРАТЬ
| РегистрНакопления.ОстаткиТоваров.Номенклатура КАК Номенклатура,
| СУММА(РегистрНакопления.ОстаткиТоваров.КоличествоОстаток) КАК Остаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров КАК РегистрНакопления.ОстаткиТоваров
|ГДЕ
| РегистрНакопления.ОстаткиТоваров.Склад = &Склад
|СГРУППИРОВАТЬ ПО
| РегистрНакопления.ОстаткиТоваров.Номенклатура";
Запрос1.УстановитьПараметр("Склад", Склад);
Результат1 = Запрос1.Выполнить();
Выборка1 = Результат1.Выбрать();
// 2. Сохраняем результат во временную таблицу
ВременнаяТаблица = Новый ТаблицаЗначений;
ВременнаяТаблица.Колонки.Добавить("Номенклатура");
ВременнаяТаблица.Колонки.Добавить("Остаток");
Пока Выборка1.Следующий() Цикл
НоваяСтрока = ВременнаяТаблица.Добавить();
НоваяСтрока.Номенклатура = Выборка1.Номенклатура;
НоваяСтрока.Остаток = Выборка1.Остаток;
КонецЦикла;
// 3. Второй запрос: уточняем данные по номенклатуре
Запрос2 = Новый Запрос;
Запрос2.Текст =
"ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ВТ.Остаток КАК Остаток,
| Справочник.Номенклатура.Артикул КАК Артикул
|ИЗ
| &ВТ КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Справочник.Номенклатура
| ПО ВТ.Номенклатура = Справочник.Номенклатура.Ссылка";
Запрос2.УстановитьПараметр("ВТ", ВременнаяТаблица);
Результат2 = Запрос2.Выполнить();
Этот метод прост, но имеет недостаток: данные временной таблицы хранятся в оперативной памяти, что может замедлить работу при больших объёмах (более 10 000 строк).
Если во втором запросе нужно использовать результат первого как параметр (например, для фильтрации), передавайте его через УстановитьПараметр с типом ТаблицаЗначений. Это безопаснее, чем конструкции вида В(&Список), которые могут вызвать ошибки при сложных данных.
2.2. Вложенные запросы (для среднего уровня)
Если второй запрос логически зависит от первого, его можно вложить прямо в текст основного запроса. Это уменьшает количество кода и улучшает читаемость:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Клиенты.Наименование КАК Клиент,
| (ВЫБРАТЬ СУММА(Документ.РеализацияТоваровУслуг.СуммаДокумента)
| ИЗ Документ.РеализацияТоваровУслуг КАК Документ.РеализацияТоваровУслуг
| ГДЕ Документ.РеализацияТоваровУслуг.Контрагент = Клиенты.Ссылка) КАК ОбщаяСуммаЗаказов
|ИЗ
| Справочник.Контрагенты КАК Клиенты
|ГДЕ
| Клиенты.ПометкаУдаления = ЛОЖЬ";
Результат = Запрос.Выполнить();
Такой подход удобен для агрегирующих функций (например, СУММА, МАКСИМУМ), но может тормозить при сложных вложенных конструкциях. Например, если во вложенном запросе используется СГРУППИРОВАТЬ ПО, платформа 1С может построить неоптимальный план выполнения.
2.3. Менеджер временных таблиц (для профессионалов)
Для работы с большими данными (от 50 000 строк) используйте МенеджерВременныхТаблиц. Этот метод позволяет:
- 📊 Хранить промежуточные результаты в базе данных, а не в памяти.
- 🔄 Повторно использовать временные таблицы в нескольких запросах.
- ⚡ Ускорить выполнение за счёт индексирования.
// 1. Создаём менеджер
МенеджерВТ = Новый МенеджерВременныхТаблиц;
// 2. Первый запрос: заполняем временную таблицу
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос1.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| СУММА(ОстаткиТоваров.КоличествоОстаток) КАК Остаток
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрНакопления.ОстаткиТоваров КАК ОстаткиТоваров
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ОстаткиТоваров.Номенклатура = Номенклатура.Ссылка
|ГДЕ
| ОстаткиТоваров.Склад = &Склад
|СГРУППИРОВАТЬ ПО
| Номенклатура.Ссылка";
Запрос1.УстановитьПараметр("Склад", Склад);
Запрос1.Выполнить();
// 3. Второй запрос: используем данные из ВТ_Остатки
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст =
"ВЫБРАТЬ
| ВТ_Остатки.Номенклатура КАК Номенклатура,
| ВТ_Остатки.Остаток КАК Остаток,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.ПолноеНаименование КАК ПолноеНаименование
|ИЗ
| ВТ_Остатки КАК ВТ_Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВТ_Остатки.Номенклатура = Номенклатура.Ссылка
|ГДЕ
| ВТ_Остатки.Остаток > 0";
Результат = Запрос2.Выполнить();
Этот метод требует больше кода, но даёт кратное ускорение при работе с большими объёмами данных. Например, в тестах на базе с 200 000 строк временная таблица в памяти выполнялась 12 секунд, а через МенеджерВременныхТаблиц — 2 секунды.
Менеджер временных таблиц автоматически очищает данные после завершения сеанса. Если вам нужно сохранить таблицу для других пользователей, используйте РегистрыСведений с признаком Временный.
3. Типичные ошибки и как их избежать
Даже опытные разработчики 1С сталкиваются с проблемами при работе с несколькими запросами. Вот самые распространённые ошибки и способы их решения:
3.1. "Неопределённое поведение при соединении временных таблиц"
Ошибка возникает, когда вы пытаетесь соединить временную таблицу с другой таблицей по полю, которое не является уникальным. Например:
// ОШИБКА: Поле "Наименование" не уникально
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Наименование КАК Наименование,
| Справочник.Номенклатура.Артикул КАК Артикул
|ИЗ
| &ВТ КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Справочник.Номенклатура
| ПО ВТ.Наименование = Справочник.Номенклатура.Наименование";
Решение: всегда соединяйте таблицы по ссылке (например, ВТ.Номенклатура = Справочник.Номенклатура.Ссылка), а не по наименованию или коду.
3.2. Утечка памяти при больших временных таблицах
Если вы создаёте временную таблицу в цикле (например, в обработчике события), но не очищаете её, это приводит к постепенному замедлению системы. Пример проблемного кода:
Для Каждого Элемент Из СписокЦен Цикл
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ_Цены";
Запрос.Выполнить();
// ... обработка ...
// ЗДЕСЬ НЕТ ОЧИСТКИ ВТ_Цены!
КонецЦикла;
Решение: используйте МенеджерВременныхТаблиц.Очистить() или явно удаляйте таблицы после использования:
МенеджерВТ.ВременныеТаблицы.Удалить("ВТ_Цены");
⚠️ Внимание: В 1С:Предприятие 8.3.20+ при использованииПОМЕСТИТЬбезМенеджерВременныхТаблицвременные таблицы автоматически удаляются после выполнения запроса. Однако в более ранних версиях (8.3.10–8.3.19) это может привести к ошибкеТаблица не найдена.
3.3. Ошибка блокировки данных в транзакциях
Если второй запрос выполняется внутри транзакции, он может заблокировать таблицы базы данных, что приведёт к зависанию других пользователей. Типичный сценарий:
НачатьТранзакцию();
Запрос1.Выполнить(); // Блокирует регистр ОстаткиТоваров
Запрос2.Выполнить(); // Пытается прочитать тот же регистр → взаимоблокировка
ЗафиксироватьТранзакцию();
Решение:
- 🔄 Разбивайте транзакции на мелкие части.
- 📌 Используйте
УровеньИзоляцииТранзакции.ПовторяемоеЧтениедля чтения данных. - 🚫 Избегайте длинных транзакций (более 5 секунд).
4. Оптимизация: как ускорить второй запрос
Два запроса вместо одного — это всегда компромисс между удобством и производительностью. Чтобы минимизировать потери скорости, следуйте этим правилам:
4.1. Индексируйте временные таблицы
Если во втором запросе вы фильтруете или соединяете данные по определённому полю, добавьте индекс:
Запрос1.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| СУММА(Остатки.Количество) КАК Остаток
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ ...
|ИНДЕКСИРОВАТЬ ПО Номенклатура"; // ← Добавляем индекс
Это ускорит соединение во втором запросе в 2–5 раз.
4.2. Используйте РАЗМЕСТИТЬ вместо ПОМЕСТИТЬ для больших данных
Конструкция РАЗМЕСТИТЬ создаёт временную таблицу на сервере, а не в памяти клиента. Это полезно для таблиц размером более 100 000 строк:
Запрос1.Текст =
"ВЫБРАТЬ ...
|РАЗМЕСТИТЬ ВТ_БольшиеДанные"; // ← Вместо ПОМЕСТИТЬ
⚠️ Внимание: РАЗМЕСТИТЬ требует прав на создание временных таблиц на сервере. В некоторых облачных решениях (например, 1С:Fresh) эта функция может быть ограничена.
4.3. Объединяйте запросы через ОБЪЕДИНИТЬ
Если второй запрос дублирует логику первого, но с другим фильтром, используйте ОБЪЕДИНИТЬ:
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Остатки.Количество КАК Остаток
|ИЗ ...
|ГДЕ Остатки.Склад = &Склад1
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Остатки.Количество КАК Остаток
|ИЗ ...
|ГДЕ Остатки.Склад = &Склад2";
Это сокращает количество обращений к базе данных.
Использовать индексы для полей соединения|Заменить ПОМЕСТИТЬ на РАЗМЕСТИТЬ для больших данных|Объединить запросы через ОБЪЕДИНИТЬ, если логика схожа|Очищать временные таблицы после использования|Проверять план выполнения в Консоли запросов-->
5. Практический пример: отчёт по продажам с детализацией
Рассмотрим реальную задачу: нужно создать отчёт, который показывает:
- Общую сумму продаж по клиентам.
- Детализацию по каждому документу реализации.
Для этого потребуется два запроса:
// 1. Первый запрос: агрегируем данные по клиентам
Запрос1 = Новый Запрос;
Запрос1.Текст =
"ВЫБРАТЬ
| Клиент.Ссылка КАК Клиент,
| Клиент.Наименование КАК НаименованиеКлиента,
| СУММА(Док.СуммаДокумента) КАК ОбщаяСумма
|ПОМЕСТИТЬ ВТ_Клиенты
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Клиент
| ПО Док.Контрагент = Клиент.Ссылка
|ГДЕ
| Док.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
|СГРУППИРОВАТЬ ПО
| Клиент.Ссылка,
| Клиент.Наименование";
Запрос1.УстановитьПараметр("ДатаНачала", НачалоМесяца(ТекущаяДата()));
Запрос1.УстановитьПараметр("ДатаОкончания", КонецМесяца(ТекущаяДата()));
Запрос1.Выполнить();
// 2. Второй запрос: получаем детализацию по документам
Запрос2 = Новый Запрос;
Запрос2.Текст =
"ВЫБРАТЬ
| ВТ_Клиенты.Клиент КАК Клиент,
| ВТ_Клиенты.НаименованиеКлиента КАК НаименованиеКлиента,
| ВТ_Клиенты.ОбщаяСумма КАК ОбщаяСумма,
| Док.Ссылка КАК Документ,
| Док.Номер КАК НомерДокумента,
| Док.Дата КАК ДатаДокумента,
| Док.СуммаДокумента КАК СуммаДокумента
|ИЗ
| ВТ_Клиенты КАК ВТ_Клиенты
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Док
| ПО ВТ_Клиенты.Клиент = Док.Контрагент
|ГДЕ
| Док.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
|УПОРЯДОЧИТЬ ПО
| ВТ_Клиенты.НаименованиеКлиента,
| Док.Дата";
Запрос2.УстановитьПараметр("ДатаНачала", НачалоМесяца(ТекущаяДата()));
Запрос2.УстановитьПараметр("ДатаОкончания", КонецМесяца(ТекущаяДата()));
Результат = Запрос2.Выполнить();
В результате мы получаем таблицу, где каждая строка клиента сопровождается списком его документов. Этот подход удобен для построения иерархических отчётов (например, в СКД).
Как экспортировать результат в СКД?
Чтобы передать результат второго запроса в систему компоновки данных (СКД), используйте следующий код:
СхемаКомпоновки = ПолучитьСхемуКомпоновкиДанных("ИмяСхемы");
Настройки = КомпоновщикНастроек.ПолучитьНастройкиПоУмолчанию(СхемаКомпоновки);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(Макет);
ПроцессорКомпоновки.ЗагрузитьДанные(Результат); // ← Передаём результат второго запроса
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанных;
РезультатВывода = ПроцессорВывода.Вывести(ПроцессорКомпоновки);
6. Альтернативные подходы: когда второй запрос не нужен
Иногда задачу можно решить без второго запроса. Рассмотрим три альтернативы:
6.1. Виртуальные таблицы
Если вам нужны остатки или обороты, используйте виртуальные таблицы регистров:
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиТоваровОстатки.Номенклатура КАК Номенклатура,
| ОстаткиТоваровОстатки.КоличествоОстаток КАК Остаток,
| ОстаткиТоваровОбороты.КоличествоПриход КАК Приход,
| ОстаткиТоваровОбороты.КоличествоРасход КАК Расход
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(&ДатаОкончания) КАК ОстаткиТоваровОстатки
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Обороты(&ДатаНачала, &ДатаОкончания) КАК ОстаткиТоваровОбороты
| ПО ОстаткиТоваровОстатки.Номенклатура = ОстаткиТоваровОбороты.Номенклатура";
6.2. Подзапросы в полях
Если нужно добавить одно поле, используйте подзапрос прямо в ВЫБРАТЬ:
Запрос.Текст =
"ВЫБРАТЬ
| Клиенты.Наименование КАК Клиент,
| (ВЫБРАТЬ ПЕРВЫЕ 1 СУММА(Док.СуммаДокумента)
| ИЗ Документ.РеализацияТоваровУслуг КАК Док
| ГДЕ Док.Контрагент = Клиенты.Ссылка) КАК ПоследняяСуммаЗаказа
|ИЗ Справочник.Контрагенты КАК Клиенты";
6.3. Объединение через СОЕДИНЕНИЕ
Если данные можно получить в одном запросе, используйте различные типы соединений:
Запрос.Текст =
"ВЫБРАТЬ
| Клиенты.Наименование КАК Клиент,
| Договоры.Номер КАК НомерДоговора,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| Справочник.Контрагенты КАК Клиенты
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
| ПО Клиенты.Ссылка = Договоры.Контрагент
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(&Дата) КАК Остатки
| ПО Договоры.Контрагент = Остатки.Контрагент";
Эти методы часто оказываются быстрее, чем два отдельных запроса, особенно в облачных решениях, где задержка сети влияет на производительность.
7. Частые вопросы и ответы
Можно ли сделать второй запрос в обработчике события формы (например, при изменении поля)?
Да, но с оговорками:
- В управляемых формах второй запрос может вызвать рекурсивное обновление данных и зависание интерфейса. Используйте
Отложенное выполнение:
Процедура ПриИзмененииПоля(Элемент)
ОтложенноеВыполнение.ДобавитьВОчередь(
Новый ОписаниеОперации("ВыполнитьЗапрос", ЭтотОбъект, "Параметр1, Параметр2")
);
КонецПроцедуры
Процедура ВыполнитьЗапрос(Парам1, Парам2) Экспорт
// Код запроса здесь
КонецПроцедуры
В обычных формах ограничений нет, но следите за блокировками данных.
Как передать результат первого запроса во второй, если он очень большой (более 100 000 строк)?
Для больших данных:
- Используйте
МенеджерВременныхТаблицсРАЗМЕСТИТЬ. - Если данных слишком много, разбивайте запрос на пакеты (по 50 000 строк) с помощью
ПЕРВЫЕиПРОПУСТИТЬ:
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 50