Разработка и Конфигурация · 12 мин
Как сделать вложенный запрос в 1С
Как сделать вложенный запрос в 1С: синтаксис подзапросов в языке запросов, примеры кода, виртуальные таблицы, временные таблицы и типичные ошибки разработчиков.
Вложенный запрос в 1С — это запрос, помещённый внутрь другого запроса в круглых скобках. Он используется, когда нужно сначала отобрать или сгруппировать данные, а затем работать с результатом как с обычной таблицей: соединять его с другими данными, фильтровать или агрегировать повторно. Типичный пример — получить список контрагентов, у которых сумма продаж за период превышает среднюю по базе.
Язык запросов 1С поддерживает подзапросы практически везде: в секции ИЗ (как источник данных), в условиях ГДЕ (через В, ИМЕЮЩИЕ, сравнения), в списке выбираемых полей. Синтаксис близок к классическому SQL, но имеет свои ограничения — например, нельзя использовать ИТОГИ внутри подзапроса, а обращение к полям результата требует аккуратной работы с псевдонимами.
Материал рассчитан на разработчиков, которые уже умеют писать простые запросы к справочникам и регистрам, и хотят освоить подзапросы, временные таблицы и типовые приёмы оптимизации. Все примеры приведены на встроенном языке 1С и языке запросов платформы.
Что такое вложенный запрос и когда он нужен
Вложенный запрос (подзапрос) — это конструкция вида (ВЫБРАТЬ ... ИЗ ...), размещённая внутри другого запроса. Результат подзапроса обрабатывается первым, а затем внешний запрос работает с ним как с обычной таблицей или набором значений. Платформа 1С транслирует такой запрос в SQL, и СУБД выполняет его как единое целое.
Подзапросы оправданы в нескольких ситуациях: когда нужно отфильтровать данные по агрегированному значению (например, документы с суммой выше средней), когда требуется соединить таблицу с результатом группировки, когда нужно проверить вхождение значения в выборку из другой таблицы. Без подзапроса такие задачи потребовали бы выгрузки данных в код и обхода коллекции в цикле — это медленнее и громоздче.
Важно понимать границу применимости: если логика требует трёх-четырёх уровней вложенности, запрос становится трудно читать и отлаживать. В таких случаях правильнее разбить его на пакетный запрос с временными таблицами — об этом ниже.
Подзапрос в секции ИЗ как источник данных
Самый частый сценарий — поместить подзапрос в секцию ИЗ и соединить его результат с другой таблицей. Подзапрос обязательно должен иметь псевдоним, иначе платформа выдаст ошибку синтаксиса. Все поля, которые нужны снаружи, должны быть явно перечислены в списке выборки подзапроса.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
Продажи.СуммаПродаж КАК СуммаПродаж
ИЗ
(ВЫБРАТЬ
ПродажиОбороты.Контрагент КАК Контрагент,
СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаПродаж
ИЗ
РегистрНакопления.Продажи.Обороты(, , , ) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Контрагент) КАК Продажи
ГДЕ
Продажи.СуммаПродаж > 100000
В этом примере внутренний запрос группирует обороты регистра по контрагентам, а внешний отбирает только тех, чья сумма превышает порог. Обратите внимание: псевдоним Продажи присвоен подзапросу, и внешний запрос обращается к полям через него.
Такой подзапрос можно соединять с другими таблицами обычными соединениями ЛЕВОЕ, ВНУТРЕННЕЕ и так далее. Например, левое соединение со справочником контрагентов позволит вывести дополнительные реквизиты — телефон, адрес, ответственного менеджера.
Подзапрос в условии ГДЕ и оператор В
Второй типовой сценарий — фильтрация по результату другого запроса. Для этого используется оператор В с подзапросом в скобках. Пример: выбрать товары, которые хотя бы раз продавались за период.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
Продажи.Номенклатура
ИЗ
РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, , ) КАК Продажи)
Здесь подзапрос возвращает одно поле — список номенклатуры из оборотов, а внешний запрос отбирает элементы справочника, входящие в этот список. Использование РАЗЛИЧНЫЕ внутри подзапроса сокращает объём данных для сравнения, хотя оптимизатор СУБД часто делает это самостоятельно.
Обратная задача решается через НЕ В — например, найти товары без движений. Однако с отрицанием есть тонкость: на больших таблицах конструкция НЕ В может работать медленнее, чем левое соединение с проверкой на NULL. Если запрос тормозит, попробуйте переписать его через ЛЕВОЕ СОЕДИНЕНИЕ и условие Подзапрос.Поле ЕСТЬ NULL.
Сравнение вариантов
Короткая и читаемая запись для фильтрации. Подходит для небольших и средних выборок.
Часто быстрее для отрицательных условий (НЕ В) на больших таблицах, но запись длиннее.
Лучший вариант, если результат подзапроса используется несколько раз или логика многоступенчатая.
Сравнение с агрегатом: скалярный подзапрос
Подзапрос может возвращать одно значение — тогда его используют в сравнении. Классическая задача: найти документы с суммой выше средней по всем документам за период.
ВЫБРАТЬ
Реализация.Ссылка КАК Документ,
Реализация.СуммаДокумента КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.СуммаДокумента >
(ВЫБРАТЬ
СРЕДНЕЕ(Док.СуммаДокумента)
ИЗ
Документ.РеализацияТоваровУслуг КАК Док
ГДЕ
Док.Дата МЕЖДУ &НачПериода И &КонПериода
И Док.Проведен)
Такой подзапрос называется скалярным: он обязан вернуть ровно одну строку и одно поле. Если условия внутри подзапроса не дадут ни одной строки, агрегатная функция СРЕДНЕЕ вернёт NULL, и сравнение не отберёт ни одной записи — это нужно учитывать при проверке результата.
Для надёжности можно обернуть агрегат в ЕСТЬNULL(..., 0), чтобы при пустой выборке подставлялось нейтральное значение. Это особенно важно в отчётах, где пользователь может задать период без данных.
Временные таблицы как альтернатива глубокой вложенности
Когда логика требует нескольких последовательных этапов отбора и группировки, вложенные подзапросы превращаются в многоуровневую «матрёшку», которую сложно читать и отлаживать. Платформа предлагает более удобный механизм — пакетные запросы с временными таблицами.
ВЫБРАТЬ
ПродажиОбороты.Контрагент КАК Контрагент,
СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаПродаж
ПОМЕСТИТЬ ОборотыПоКонтрагентам
ИЗ
РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, , ) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Контрагент
;
ВЫБРАТЬ
Обороты.Контрагент КАК Контрагент,
Обороты.СуммаПродаж КАК СуммаПродаж,
Контрагенты.ОсновнойМенеджер КАК Менеджер
ИЗ
ОборотыПоКонтрагентам КАК Обороты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
ПО Обороты.Контрагент = Контрагенты.Ссылка
Первый запрос пакета помещает сгруппированные обороты во временную таблицу ОборотыПоКонтрагентам, второй — читает её как обычный источник. Каждый шаг можно проверить отдельно в консоли запросов, что резко упрощает поиск ошибок. Временные таблицы живут в пределах одного объекта Запрос и удаляются автоматически либо явно через УНИЧТОЖИТЬ.
- 1Сформулируйте, какие данные нужны на каждом этапе: фильтрация, группировка, соединение
- 2Напишите первый запрос пакета с ПОМЕСТИТЬ во временную таблицу
- 3Проверьте содержимое временной таблицы в консоли запросов отдельным запросом
- 4Напишите финальный запрос, читающий временную таблицу и соединяющий её с нужными справочниками
- 5Выполните весь пакет и сверьте итог с контрольными данными из отчёта конфигурации
В коде 1С пакетный запрос выполняется стандартно: текст присваивается свойству Запрос.Текст, параметры устанавливаются через УстановитьПараметр, а результат получают методом Выполнить() или ВыполнитьПакет() — последний возвращает массив результатов по каждому запросу пакета.
Типичные ошибки и ограничения
Первая частая ошибка — отсутствие псевдонима у подзапроса в секции ИЗ. Платформа не выполнит такой запрос и сообщит о синтаксической ошибке. Вторая — попытка обратиться снаружи к полю, которое не перечислено в списке выборки подзапроса: внешний запрос «видит» только то, что подзапрос явно вернул.
Третья группа проблем связана с производительностью. Подзапрос в секции ГДЕ, ссылающийся на виртуальную таблицу без отбора по периоду, заставит СУБД строить обороты по всей истории регистра. Всегда передавайте параметры периода и отборы внутрь подзапроса, а не фильтруйте его результат снаружи.
Ещё одно ограничение: результат скалярного подзапроса, вернувшего больше одной строки, приведёт к ошибке выполнения. Если есть вероятность нескольких строк, добавьте агрегирование или ПЕРВЫЕ 1 с осознанным упорядочиванием — но помните, что УПОРЯДОЧИТЬ в подзапросе недопустимо, поэтому ПЕРВЫЕ 1 без сортировки вернёт произвольную строку.
Как проверить и отладить вложенный запрос
Основной инструмент отладки — консоль запросов (встроенная в режиме предприятия через меню разработчика или внешняя обработка). Правильная методика — проверять подзапрос отдельно: скопируйте его текст, выполните как самостоятельный запрос и убедитесь, что он возвращает ожидаемые строки и количество записей.
Далее проверьте соединение: если внешний запрос возвращает меньше строк, чем ожидалось, причина почти всегда в условии связи или в дублях. Полезно временно заменить ВНУТРЕННЕЕ соединение на ЛЕВОЕ и посмотреть, какие строки не нашли пару — это сразу показывает, где теряются данные.
Как проверить результат
Возвращает ожидаемые поля и разумное число строк
Пустой результат или ошибка синтаксиса
Совпадает с контрольной выборкой или отчётом конфигурации
Строк меньше или больше из-за дублей соединения
Запрос отрабатывает за секунды на рабочем объёме
Многоминутное выполнение — нет отборов внутри подзапроса
Если один и тот же подзапрос встречается в тексте дважды (например, в ИЗ и в ГДЕ), перенесите его во временную таблицу — это устранит дублирование кода и риск расхождения условий при будущих правках.
Часто задаваемые вопросы
Можно ли использовать несколько уровней вложенности?
Технически платформа позволяет вкладывать подзапросы друг в друга. На практике больше двух уровней использовать не стоит: читаемость падает, а отладка становится почти невозможной. Разбейте логику на временные таблицы пакетного запроса.
Чем подзапрос отличается от временной таблицы?
Подзапрос существует только внутри одного запроса и не может быть проверен отдельно без копирования текста. Временная таблица создаётся отдельным запросом пакета, её содержимое можно посмотреть между шагами и использовать в нескольких последующих запросах пакета.
Почему запрос с НЕ В работает медленно?
На больших таблицах СУБД может строить неэффективный план для отрицательного условия. Перепишите условие через ЛЕВОЕ СОЕДИНЕНИЕ с проверкой ЕСТЬ NULL — такой вариант обычно выполняется быстрее и предсказуемее.
Можно ли передать таблицу значений в подзапрос?
Напрямую в подзапрос — нет, но можно передать таблицу значений как параметр и поместить её во временную таблицу конструкцией ВЫБРАТЬ ... ПОМЕСТИТЬ ИЗ &Таблица, а затем соединять её с другими данными, включая подзапросы.
Работают ли подзапросы в СКД?
Да, в схеме компоновки данных текст запроса набора данных поддерживает те же конструкции: подзапросы в ИЗ и ГДЕ, временные таблицы. Дополнительно СКД позволяет использовать характеристики и вычисляемые поля поверх результата.
Что важно запомнить
Вложенный запрос в 1С пишется в круглых скобках — в ИЗ как источник с обязательным псевдонимом или в ГДЕ через оператор В. Внутри подзапроса нельзя использовать ИТОГИ и УПОРЯДОЧИТЬ. При сложной логике заменяйте вложенность пакетным запросом с временными таблицами и всегда проверяйте каждый шаг в консоли запросов на копии базы.