Работа с данными в 1С:Предприятие 8 немыслима без языка запросов. Одним из самых мощных‚ но требующих осторожности инструментов являются вложенные запросы (подзапросы). Материал ниже основан на актуальных данных сообщества разработчиков‚ курсах (например‚ Курсы-по-1С.рф)‚ документации 1s-up.ru‚ Microsoft Learn (по SQL-аналогам) и стандартах разработки (ССМК) на .
Теория: Что такое вложенный запрос?
Вложенный запрос (подзапрос) — это оператор ВЫБРАТЬ‚ размещенный внутри другого оператора ВЫБРАТЬ‚ ВЫБРАТЬ ... ПОМЕСТИТЬ‚ ОБЪЕДИНИТЬ или в условиях отбора (ГДЕ‚ ИМЕЮЩИЕ)‚ а также в условиях соединения (СОЕДИНЕНИЕ).
Важное уточнение терминологии (обсуждалось на Курсы-по-1С.рф‚ ): Понятия «Подзапрос» и «Вложенный запрос» в контексте 1С часто используются как синонимы. Однако строго говоря‚ подзапрос — это запрос‚ вложенный в условие (ГДЕ/ИМЕЮЩИЕ/СОЕДИНЕНИЕ)‚ а вложенный запрос в широком смысле может использоваться и в списке полей (скалярный подзапрос)‚ и в качестве виртуальной таблицы в секции
ИЗ.
Классификация по месту использования
- В секции ИЗ (Derived Table / Табличное выражение): Результат подзапроса выступает временной таблицей для основного запроса.
- В списке полей (Скалярный подзапрос): Должен возвращать ровно одно значение (одну строку‚ одно поле). Используется для вычисляемых колонок.
- В условии ГДЕ / ИМЕЮЩИЕ (Predicate Subquery): Используется с операторами
В‚НЕ В‚СУЩЕСТВУЕТ‚НЕ СУЩЕСТВУЕТ‚>‚=‚<и др. - В условии соединения (ОН): Позволяет соединять таблицы по сложным вычисляемым условиям. Внимание: Есть серьезные ограничения производительности (см. раздел 4).
Синтаксис и базовые примеры
Синтаксис в 1С близок к SQL-стандарту (Microsoft Learn)‚ но имеет свои особенности (например‚ пакетные запросы и временные таблицы).
Простой вложенный запрос в секции ИЗ
Пример из практики (1s-up.ru‚ ): обращение к выборке по названию таблицы.
ВЫБРАТЬ
ВложенныйЗапрос.Номенклатура‚
ВложенныйЗапрос.СуммаОборот КАК ОбъемПродаж
ИЗ
(ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура‚
СУММА(Продажи.Сумма) КАК СуммаОборот
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Продажи.Номенклатура) КАК ВложенныйЗапрос
ГДЕ
ВложенныйЗапрос.СуммаОборот > 100000
Пояснение: Внутри скобок формируется промежуточный набор данных (агрегат по продажам)‚ которому присваивается псевдоним ВложенныйЗапрос. Внешний запрос фильтрует уже готовый результат.
Скалярный подзапрос в списке полей
Получение последней цены номенклатуры на момент продажи.
ВЫБРАТЬ
Продажи.Номенклатура‚
Продажи.Количество‚
(ВЫБРАТЬ ПЕРВЫЕ 1
Цены.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
ГДЕ
Цены.Номенклатура = Продажи.Номенклатура
И Цены.Период <= Продажи.Период
УПОРЯДОЧИТЬ ПО
Цены.Период УБЫВ) КАК ПоследняяЦена
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
Подзапрос в условии ГДЕ (Оператор В)
Поиск клиентов‚ у которых были продажи за текущий месяц.
ВЫБРАТЬ
Клиенты.Наименование
ИЗ
Справочник.Контрагенты КАК Клиенты
ГДЕ
Клиенты.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
Продажи.Контрагент
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &НачалоМесяца И &КонецМесяца)
Продвинутые техники: Вложенные таблицы и Пакетные запросы
Язык запросов 1С позволяет не просто вкладывать SELECT в SELECT‚ а управлять потоком данных через временные таблицы и пакеты. Это часто эффективнее чистых вложенных запросов.
Временные таблицы vs Вложенные запросы
Согласно обсуждениям на 1s-up.ru и стандартам ССМК: Консоль запросов позволяет выполнять выделенные фрагменты. Временные таблицы (ПОМЕСТИТЬ ВТ_Имя) имеют критическое преимущество:
- Могут передаваться между запросами в рамках одного пакета.
- Могут повторно использоваться многократно.
- Позволяют создавать индексы (
ИНДЕКСЫ ПО (Поле1‚ Поле2)) для ускорения последующих соединений.
Пример замены вложенного запроса на временную таблицу (Пакетный запрос):
// Запрос 1: Подготовка агрегата во временную таблицу
ВЫБРАТЬ
Продажи.Номенклатура‚
СУММА(Продажи.Сумма) КАК ОбъемПродаж
ПОМЕСТИТЬ ВТ_ПродажиПоНоменклатуре
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &Начало И &Конец
СГРУППИРОВАТЬ ПО
Продажи.Номенклатура
ИНДЕКСЫ ПО
Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
// Запрос 2: Использование временной таблицы (можно вызвать отдельно в консоли)
ВЫБРАТЬ
Ном.Наименование‚
ВТ_ПродажиПоНоменклатуре.ОбъемПродаж
ИЗ
ВТ_ПродажиПоНоменклатуре КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Ном ПО Ном.Ссылка = ВТ.Номенклатура
ГДЕ
ВТ.ОбъемПродаж > 100000
Работа с вложенными таблицами в коде (1С:Программно)
Как отмечалось на 1s-up.ru‚ результат запроса‚ содержащего вложенные таблицы (или иерархию)‚ можно обрабатывать программно:
Результат = Запрос.Выполнить;
// Получаем выборку по "внешней" таблице
Выборка = Результат.Выбрать;
Пока Выборка.Следующий Цикл
// Если в запросе была вложенная таблица (например‚ через ОБЪЕДИНИТЬ или структуру)‚
// к ней можно обратиться через точку‚ если она представлена как поле-ТаблицаЗначений
// Или‚ если это результат пакета с несколькими таблицами:
ВложеннаяТаблица = Результат["ИмяВложеннойТаблицы"].Выбрать;
// Обработка вложенной выборки...
КонецЦикла;
Оптимизация и «Подводные камни» (Best Practices)
Это самый важный раздел. Существует устойчивое мнение экспертов (проверено ‚ источники: профильные блоги‚ ССМК)‚ что вложенные запросы в сложных конструкциях выполняются платформой 1С нерационально‚ требуя больших ресурсов (RAM‚ CPU‚ TempDB) и времени‚ нежели альтернативы.
Главное правило ССМК: Не использовать вложенные запросы в условиях соединения
Источник: «Не использовать вложенные запросы в условиях соединения» :: Язык запросов 1С:Предприятия (Стандарты разработки‚ ).
⛔ Антипаттерн: Подзапрос в ОН (Условии соединения)
// ПЛОХО: Подзапрос внутри условия соединения
ВЫБРАТЬ
Т1.Поле1
ИЗ
Таблица1 КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ Таблица2 КАК Т2
ПО Т1.Ключ = Т2.Ключ
И Т2.Поле В (ВЫБРАТЬ Поле3 ИЗ Таблица3 ГДЕ Условие) // ОЧЕНЬ МЕДЛЕННО
Почему это плохо: Оптимизатор СУБД (MS SQL / PostgreSQL) часто не может «развернуть» такой подзапрос в эффективный план (Hash Join / Merge Join)‚ переходя к Nested Loops с повторным выполнением подзапроса для каждой строки.
✅ Хорошо: Предварительное материализация через Временную Таблицу
// ХОРОШО: Пакетный запрос с ВТ
ВЫБРАТЬ Поле3 ПОМЕСТИТЬ ВТ_Фильтр ИЗ Таблица3 ГДЕ Условие ИНДЕКСЫ ПО Поле3;
ВЫБРАТЬ Т1.Поле1
ИЗ Таблица1 КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ Таблица2 КАК Т2 ПО Т1.Ключ = Т2.Ключ
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК Ф ПО Т2.Поле = Ф.Поле3
Оператор ОБЪЕДИНИТЬ и «Галка без дублей»
Вопрос с Курсы-по-1С.рф: «Если при объединении запросов поставить галку «Без дублей» (DISTINCT) на первый запрос‚ это повлияет на что-нибудь?»
Ответ: Да‚ критически. ОБЪЕДИНИТЬ (UNION) по умолчанию убирает дубликаты (как UNION DISTINCT в SQL)‚ что требует дорогой операции сортировки/хеширования всего результата. ОБЪЕДИНИТЬ ВСЕ (UNION ALL) просто склеивает потоки данных.
- Если вы ставите «Без дублей» (DISTINCT) на первый запрос пакета перед UNION — вы тратите ресурсы на дедупликацию промежуточного результата‚ который все равно пройдет дедупликацию при финальном UNION (если не UNION ALL).
- Рекомендация: Всегда используйте
ОБЪЕДИНИТЬ ВСЕ‚ если бизнес-логика допускает дубликаты или вы гарантируете их отсутствие на уровне данных. ЯвныйРАЗЛИЧНЫЕ(DISTINCT) ставьте только в самом внешнем запросе или в финальном шаге.
Коррелированные подзапросы (Correlated Subqueries)
Подзапрос‚ ссылающийся на поля внешнего запроса (как пример в п. 2.2 с Цены.Номенклатура = Продажи.Номенклатура).
- Выполняются для каждой строки внешнего запроса (Row-by-agonizing-row, RBAR).
- На больших выборках (тысячи строк) — гарантированный таймаут или зависание.
- Лечение: Замена на
ЛЕВОЕ СОЕДИНЕНИЕс предварительно агрегированной временной таблицей (см. п. 3.1).
Чек-лист разработчика: Когда что применять
| Сценарий | Рекомендуемый подход | Комментарий |
|---|---|---|
| Простая фильтрация (список значений < 1000) | ГДЕ Поле В (Подзапрос) | Читаемо‚ оптимизатор справляется хорошо. |
| Сложная агрегация перед соединением | Временная таблица (ПОМЕСТИТЬ ВТ_…) в пакете | Контроль индексов‚ повторное использование‚ план выполнения предсказуем; |
| Получение одного значения на строку (последняя цена‚ остаток) | ЛЕВОЕ СОЕДИНЕНИЕ с ВТ / Виртуальной таблицей среза последних | Избегайте скалярных подзапросов в списке полей на больших объемах. |
| Условие соединения зависит от подзапроса | Вынести подзапрос в ВТ‚ соединить ВТ | Строгое требование ССМК (п. 4.1). |
| Рекурсивные запросы / Иерархии | Иерархические запросы (ИЕРАРХИЯ) или ВТ в цикле (на сервере) | Вложенные запросы не поддерживают рекурсию напрямую. |
Отладка в Консоли Запросов
Совет от 1s-up.ru : Консоль запросов 1С позволяет выполнять выделенную часть текста;
- Выделите код вложенного запроса (внутри скобок после
ИЗили вГДЕ). - Нажмите «Выполнить» (F5).
- Анализируйте результат и план запроса (вкладка «План») изолированно.
- Так вы локализуете узкое место: тормозит ли сам подзапрос или его соединение с внешней таблицей.
Резюме
Вложенные запросы в 1С — это стандартный синтаксический сахар‚ который делает код компактным. Однако на платформе 1С:Предприятие 8.3 (актуально на ) производительность сложных отчетов почти всегда выигрывает от рефакторинга вложенных запросов в пакеты с временными таблицами;
Главные выводы:
- Используйте вложенные запросы в
ИЗиГДЕдля простых и средних задач. - Категорически избегайте вложенных запросов в условиях
СОЕДИНЕНИЕ ... ПО(ON clause). - Заменяйте коррелированные подзапросы и скалярные подзапросы в списке полей на
ЛЕВОЕ СОЕДИНЕНИЕс предварительно рассчитанными Временными Таблицами. - Используйте
ОБЪЕДИНИТЬ ВСЕвместоОБЪЕДИНИТЬвезде‚ где это допустимо логикой. - Отлаживайте части запроса отдельно в Консоли Запросов.
Статья подготовлена на основе открытых источников: Курсы-по-1С.рф‚ 1s-up.ru‚ Документация 1С (ССМК)‚ Microsoft Learn‚ профильные блоги оптимизации. Дата актуализации: .