К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как сделать вложенный запрос в 1С

Как сделать вложенный запрос в 1С: синтаксис подзапросов в языке запросов, примеры кода, виртуальные таблицы, временные таблицы и типичные ошибки разработчиков.

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

Язык запросов 1С поддерживает подзапросы практически везде: в секции ИЗ (как источник данных), в условиях ГДЕ (через В, ИМЕЮЩИЕ, сравнения), в списке выбираемых полей. Синтаксис близок к классическому SQL, но имеет свои ограничения — например, нельзя использовать ИТОГИ внутри подзапроса, а обращение к полям результата требует аккуратной работы с псевдонимами.

Материал рассчитан на разработчиков, которые уже умеют писать простые запросы к справочникам и регистрам, и хотят освоить подзапросы, временные таблицы и типовые приёмы оптимизации. Все примеры приведены на встроенном языке 1С и языке запросов платформы.

Что такое вложенный запрос и когда он нужен

Вложенный запрос (подзапрос) — это конструкция вида (ВЫБРАТЬ ... ИЗ ...), размещённая внутри другого запроса. Результат подзапроса обрабатывается первым, а затем внешний запрос работает с ним как с обычной таблицей или набором значений. Платформа 1С транслирует такой запрос в SQL, и СУБД выполняет его как единое целое.

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

Важно понимать границу применимости: если логика требует трёх-четырёх уровней вложенности, запрос становится трудно читать и отлаживать. В таких случаях правильнее разбить его на пакетный запрос с временными таблицами — об этом ниже.

Подзапрос в секции ИЗ как источник данных

Самый частый сценарий — поместить подзапрос в секцию ИЗ и соединить его результат с другой таблицей. Подзапрос обязательно должен иметь псевдоним, иначе платформа выдаст ошибку синтаксиса. Все поля, которые нужны снаружи, должны быть явно перечислены в списке выборки подзапроса.

ВЫБРАТЬ

Продажи.Контрагент КАК Контрагент,

Продажи.СуммаПродаж КАК СуммаПродаж

ИЗ

(ВЫБРАТЬ

ПродажиОбороты.Контрагент КАК Контрагент,

СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаПродаж

ИЗ

РегистрНакопления.Продажи.Обороты(, , , ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО

ПродажиОбороты.Контрагент) КАК Продажи

ГДЕ

Продажи.СуммаПродаж > 100000

В этом примере внутренний запрос группирует обороты регистра по контрагентам, а внешний отбирает только тех, чья сумма превышает порог. Обратите внимание: псевдоним Продажи присвоен подзапросу, и внешний запрос обращается к полям через него.

Такой подзапрос можно соединять с другими таблицами обычными соединениями ЛЕВОЕ, ВНУТРЕННЕЕ и так далее. Например, левое соединение со справочником контрагентов позволит вывести дополнительные реквизиты — телефон, адрес, ответственного менеджера.

Подзапрос в условии ГДЕ и оператор В

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

ВЫБРАТЬ

Номенклатура.Ссылка КАК Товар

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Ссылка В

(ВЫБРАТЬ РАЗЛИЧНЫЕ

Продажи.Номенклатура

ИЗ

РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, , ) КАК Продажи)

Здесь подзапрос возвращает одно поле — список номенклатуры из оборотов, а внешний запрос отбирает элементы справочника, входящие в этот список. Использование РАЗЛИЧНЫЕ внутри подзапроса сокращает объём данных для сравнения, хотя оптимизатор СУБД часто делает это самостоятельно.

Обратная задача решается через НЕ В — например, найти товары без движений. Однако с отрицанием есть тонкость: на больших таблицах конструкция НЕ В может работать медленнее, чем левое соединение с проверкой на NULL. Если запрос тормозит, попробуйте переписать его через ЛЕВОЕ СОЕДИНЕНИЕ и условие Подзапрос.Поле ЕСТЬ NULL.

Сравнение вариантов

В (подзапрос)

Короткая и читаемая запись для фильтрации. Подходит для небольших и средних выборок.

ЛЕВОЕ СОЕДИНЕНИЕ + ЕСТЬ NULL

Часто быстрее для отрицательных условий (НЕ В) на больших таблицах, но запись длиннее.

Временная таблица

Лучший вариант, если результат подзапроса используется несколько раз или логика многоступенчатая.

Сравнение с агрегатом: скалярный подзапрос

Подзапрос может возвращать одно значение — тогда его используют в сравнении. Классическая задача: найти документы с суммой выше средней по всем документам за период.

ВЫБРАТЬ

Реализация.Ссылка КАК Документ,

Реализация.СуммаДокумента КАК Сумма

ИЗ

Документ.РеализацияТоваровУслуг КАК Реализация

ГДЕ

Реализация.СуммаДокумента >

(ВЫБРАТЬ

СРЕДНЕЕ(Док.СуммаДокумента)

ИЗ

Документ.РеализацияТоваровУслуг КАК Док

ГДЕ

Док.Дата МЕЖДУ &НачПериода И &КонПериода

И Док.Проведен)

Такой подзапрос называется скалярным: он обязан вернуть ровно одну строку и одно поле. Если условия внутри подзапроса не дадут ни одной строки, агрегатная функция СРЕДНЕЕ вернёт NULL, и сравнение не отберёт ни одной записи — это нужно учитывать при проверке результата.

Для надёжности можно обернуть агрегат в ЕСТЬNULL(..., 0), чтобы при пустой выборке подставлялось нейтральное значение. Это особенно важно в отчётах, где пользователь может задать период без данных.

Временные таблицы как альтернатива глубокой вложенности

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

ВЫБРАТЬ

ПродажиОбороты.Контрагент КАК Контрагент,

СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаПродаж

ПОМЕСТИТЬ ОборотыПоКонтрагентам

ИЗ

РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, , ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО

ПродажиОбороты.Контрагент

;

ВЫБРАТЬ

Обороты.Контрагент КАК Контрагент,

Обороты.СуммаПродаж КАК СуммаПродаж,

Контрагенты.ОсновнойМенеджер КАК Менеджер

ИЗ

ОборотыПоКонтрагентам КАК Обороты

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты

ПО Обороты.Контрагент = Контрагенты.Ссылка

Первый запрос пакета помещает сгруппированные обороты во временную таблицу ОборотыПоКонтрагентам, второй — читает её как обычный источник. Каждый шаг можно проверить отдельно в консоли запросов, что резко упрощает поиск ошибок. Временные таблицы живут в пределах одного объекта Запрос и удаляются автоматически либо явно через УНИЧТОЖИТЬ.

Порядок действий
  1. 1
    Сформулируйте, какие данные нужны на каждом этапе: фильтрация, группировка, соединение
  2. 2
    Напишите первый запрос пакета с ПОМЕСТИТЬ во временную таблицу
  3. 3
    Проверьте содержимое временной таблицы в консоли запросов отдельным запросом
  4. 4
    Напишите финальный запрос, читающий временную таблицу и соединяющий её с нужными справочниками
  5. 5
    Выполните весь пакет и сверьте итог с контрольными данными из отчёта конфигурации

В коде 1С пакетный запрос выполняется стандартно: текст присваивается свойству Запрос.Текст, параметры устанавливаются через УстановитьПараметр, а результат получают методом Выполнить() или ВыполнитьПакет() — последний возвращает массив результатов по каждому запросу пакета.

Типичные ошибки и ограничения

Первая частая ошибка — отсутствие псевдонима у подзапроса в секции ИЗ. Платформа не выполнит такой запрос и сообщит о синтаксической ошибке. Вторая — попытка обратиться снаружи к полю, которое не перечислено в списке выборки подзапроса: внешний запрос «видит» только то, что подзапрос явно вернул.

Третья группа проблем связана с производительностью. Подзапрос в секции ГДЕ, ссылающийся на виртуальную таблицу без отбора по периоду, заставит СУБД строить обороты по всей истории регистра. Всегда передавайте параметры периода и отборы внутрь подзапроса, а не фильтруйте его результат снаружи.

Типичные ошибки
01
Подзапрос в ИЗ без псевдонима — синтаксическая ошибка при выполнении
02
Обращение к полю, не выбранному внутри подзапроса — ошибка «Поле не найдено»
03
Виртуальная таблица без параметров периода внутри подзапроса — резкое падение скорости на больших базах
04
Использование ИТОГИ или УПОРЯДОЧИТЬ внутри подзапроса — запрос не выполнится
05
Глубокая вложенность вместо временных таблиц — запрос невозможно отладить по шагам

Ещё одно ограничение: результат скалярного подзапроса, вернувшего больше одной строки, приведёт к ошибке выполнения. Если есть вероятность нескольких строк, добавьте агрегирование или ПЕРВЫЕ 1 с осознанным упорядочиванием — но помните, что УПОРЯДОЧИТЬ в подзапросе недопустимо, поэтому ПЕРВЫЕ 1 без сортировки вернёт произвольную строку.

Как проверить и отладить вложенный запрос

Основной инструмент отладки — консоль запросов (встроенная в режиме предприятия через меню разработчика или внешняя обработка). Правильная методика — проверять подзапрос отдельно: скопируйте его текст, выполните как самостоятельный запрос и убедитесь, что он возвращает ожидаемые строки и количество записей.

Далее проверьте соединение: если внешний запрос возвращает меньше строк, чем ожидалось, причина почти всегда в условии связи или в дублях. Полезно временно заменить ВНУТРЕННЕЕ соединение на ЛЕВОЕ и посмотреть, какие строки не нашли пару — это сразу показывает, где теряются данные.

Контрольные точки

Как проверить результат

Подзапрос выполняется отдельно
Норма

Возвращает ожидаемые поля и разумное число строк

Тревожный признак

Пустой результат или ошибка синтаксиса

Число строк итогового запроса
Норма

Совпадает с контрольной выборкой или отчётом конфигурации

Тревожный признак

Строк меньше или больше из-за дублей соединения

Время выполнения
Норма

Запрос отрабатывает за секунды на рабочем объёме

Тревожный признак

Многоминутное выполнение — нет отборов внутри подзапроса

Если один и тот же подзапрос встречается в тексте дважды (например, в ИЗ и в ГДЕ), перенесите его во временную таблицу — это устранит дублирование кода и риск расхождения условий при будущих правках.
ПЗ
Практическое замечание

Часто задаваемые вопросы

Можно ли использовать несколько уровней вложенности?

Технически платформа позволяет вкладывать подзапросы друг в друга. На практике больше двух уровней использовать не стоит: читаемость падает, а отладка становится почти невозможной. Разбейте логику на временные таблицы пакетного запроса.

Чем подзапрос отличается от временной таблицы?

Подзапрос существует только внутри одного запроса и не может быть проверен отдельно без копирования текста. Временная таблица создаётся отдельным запросом пакета, её содержимое можно посмотреть между шагами и использовать в нескольких последующих запросах пакета.

Почему запрос с НЕ В работает медленно?

На больших таблицах СУБД может строить неэффективный план для отрицательного условия. Перепишите условие через ЛЕВОЕ СОЕДИНЕНИЕ с проверкой ЕСТЬ NULL — такой вариант обычно выполняется быстрее и предсказуемее.

Можно ли передать таблицу значений в подзапрос?

Напрямую в подзапрос — нет, но можно передать таблицу значений как параметр и поместить её во временную таблицу конструкцией ВЫБРАТЬ ... ПОМЕСТИТЬ ИЗ &Таблица, а затем соединять её с другими данными, включая подзапросы.

Работают ли подзапросы в СКД?

Да, в схеме компоновки данных текст запроса набора данных поддерживает те же конструкции: подзапросы в ИЗ и ГДЕ, временные таблицы. Дополнительно СКД позволяет использовать характеристики и вычисляемые поля поверх результата.

Итог

Что важно запомнить

Вложенный запрос в 1С пишется в круглых скобках — в ИЗ как источник с обязательным псевдонимом или в ГДЕ через оператор В. Внутри подзапроса нельзя использовать ИТОГИ и УПОРЯДОЧИТЬ. При сложной логике заменяйте вложенность пакетным запросом с временными таблицами и всегда проверяйте каждый шаг в консоли запросов на копии базы.