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

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

Что такое подзапрос в 1С и как его использовать

Что такое подзапрос в языке запросов 1С, как писать вложенные запросы в секциях ВЫБРАТЬ, ИЗ и ГДЕ, когда подзапросы лучше заменить временными таблицами.

Подзапрос в 1С — это запрос, вложенный внутрь другого запроса на встроенном языке запросов платформы. Он заключается в круглые скобки и может использоваться в секциях ВЫБРАТЬ, ИЗ и ГДЕ основного запроса. Подзапросы позволяют получать агрегированные или отфильтрованные данные и сразу использовать их при выборке, не создавая отдельных объектов в коде.

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

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

Как устроен подзапрос в языке запросов 1С

Синтаксически подзапрос выглядит как обычный запрос, заключённый в круглые скобки. Внутри скобок действуют те же правила языка запросов: секции ВЫБРАТЬ, ИЗ, ГДЕ, группировки и соединения. Внешний запрос «не видит» внутренности подзапроса — он получает только результат: одно значение, список значений или виртуальную таблицу.

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

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

Подзапрос в секции ВЫБРАТЬ: скалярные значения

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

Запрос.Текст =

"ВЫБРАТЬ

| Контрагенты.Ссылка КАК Контрагент,

| (ВЫБРАТЬ

| МАКСИМУМ(Реализация.Дата)

| ИЗ

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

| ГДЕ

| Реализация.Контрагент = Контрагенты.Ссылка) КАК ДатаПоследнейПродажи

|ИЗ

| Справочник.Контрагенты КАК Контрагенты";

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

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

В секции ИЗ подзапрос выступает как виртуальная таблица. Ему обязательно присваивается псевдоним, через который внешний запрос обращается к полям вложенной выборки. Такой приём удобен, когда нужно сначала агрегировать данные, а потом соединить результат с другой таблицей или применить дополнительный отбор.

Запрос.Текст =

"ВЫБРАТЬ

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

| Продажи.СуммаОборот КАК Сумма

|ИЗ

| (ВЫБРАТЬ

| Обороты.Номенклатура КАК Номенклатура,

| СУММА(Обороты.СуммаОборот) КАК СуммаОборот

| ИЗ

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

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

| Обороты.Номенклатура) КАК Продажи

|ГДЕ

| Продажи.СуммаОборот > 100000";

Здесь вложенный запрос сначала сворачивает обороты по номенклатуре, а внешний отбирает только крупные суммы. Тот же результат можно получить через секцию ИМЕЮЩИЕ, и во многих случаях это читабельнее. Подзапрос в ИЗ оправдан, когда логика многоступенчатая: агрегация, потом соединение, потом ещё одна агрегация.

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

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

Запрос.Текст =

"ВЫБРАТЬ

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

|ИЗ

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

|ГДЕ

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

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

| Товары.Номенклатура

| ИЗ

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

Сравнение с одиночным значением тоже возможно: ГДЕ Сумма > (ВЫБРАТЬ МАКСИМУМ(...)). Здесь действует то же требование скалярности, что и в секции ВЫБРАТЬ. Для отрицания используется НЕ В, но с ним осторожнее: если вложенный список содержит NULL, результат условия может отличаться от ожидаемого.

Типичные ошибки
01
Подзапрос в ВЫБРАТЬ без агрегатной функции возвращает несколько строк — запрос падает с ошибкой при выполнении
02
Использование НЕ В со списком, где встречается NULL — фильтр отбрасывает или пропускает строки непредсказуемо
03
Коррелированный подзапрос на большой таблице без индекса по полю связи — выборка выполняется минутами
04
Попытка использовать ИТОГИ или УПОРЯДОЧИТЬ внутри подзапроса там, где это запрещено — синтаксическая ошибка
05
Вложенность в несколько уровней ради одной выборки — текст нечитаем, а временные таблицы решают задачу проще

Подзапрос или временная таблица: что выбрать

Пакетный запрос с временными таблицами — главная альтернатива подзапросам. Результат первого запроса пакета помещается во временную таблицу (ПОМЕСТИТЬ ИмяТаблицы), а следующие запросы пакета читают её как обычный источник. Логика разбивается на понятные шаги, каждый можно отладить отдельно.

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

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

Подзапрос в ГДЕ с оператором В

Подходит для простых отборов по списку значений; ограничение — трудно отлаживать и может выполняться повторно

Подзапрос в ИЗ

Удобен для одношаговой агрегации перед соединением; при глубокой вложенности текст становится нечитаемым

Временная таблица в пакетном запросе

Лучший выбор для многоступенчатой логики и больших объёмов; требует объекта МенеджерВременныхТаблиц при работе из кода

Соединение с группировкой

Часто заменяет скалярный подзапрос в ВЫБРАТЬ; ограничение — нужно аккуратно следить за дублированием строк при соединении

Отладка и проверка запросов с подзапросами

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

При подозрении на проблемы производительности смотрите технологический журнал с событиями СУБД или план запроса средствами самой СУБД. Признак неприятностей: вложенный запрос сканирует всю таблицу при каждом обращении. Лечится индексированием полей связи или переносом логики во временную таблицу.

Порядок действий при написании подзапроса
  1. 1
    Сформулируйте, что должен вернуть вложенный запрос: одно значение, список или таблицу
  2. 2
    Напишите и выполните вложенный запрос отдельно в консоли, проверьте результат
  3. 3
    Вставьте его в скобках в нужную секцию внешнего запроса, добавьте псевдонимы
  4. 4
    Выполните весь запрос на тестовой базе и сверьте количество строк с ожиданием
  5. 5
    Замерьте время выполнения на объёме, близком к рабочему; при медленной работе перепишите через временные таблицы

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

Помимо уже названных — скалярности, NULL и запрета ИТОГИ — стоит помнить о читаемости. Подзапросы глубже второго уровня вложенности почти всегда сигнализируют, что задачу лучше декомпозировать. Коллега, который будет сопровождать этот запрос через полгода, скажет спасибо за временные таблицы с говорящими именами.

Ещё одно ограничение касается динамических списков: в произвольном запросе динамического списка подзапросы допустимы, но сложный текст может конфликтовать с механизмами динамического считывания и отборов. Проверяйте работу списка с типовыми отборами пользователя после внедрения вложенных конструкций.

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

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

Количество строк результата
Норма

Совпадает с выборкой, собранной вручную на тестовых данных

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

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

Время выполнения на рабочем объёме
Норма

Запрос выполняется за приемлемое для операции время

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

Выполнение растёт непропорционально объёму — подзапрос перевыполняется, нужна временная таблица

Значения скалярного подзапроса
Норма

Для каждой строки внешней выборки значение корректно или NULL там, где данных нет

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

Ошибка выполнения или одинаковое значение во всех строках — нарушена связь с внешним запросом

Частые вопросы о подзапросах в 1С

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

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

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

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

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

Частые причины: коррелированный подзапрос выполняется для каждой строки внешней выборки, по полям связи нет подходящего индекса, СУБД не смогла преобразовать вложенность в соединение. Диагностируйте через план запроса и технологический журнал, а лечите индексацией или переносом логики во временные таблицы.

Можно ли в подзапросе ссылаться на поля внешнего запроса?

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

Допустимы ли подзапросы в конструкторе запросов?

Да, конструктор запросов поддерживает вложенные запросы: в качестве источника на закладке «Таблицы и поля» можно добавить вложенный запрос и настроить его отдельно. Для условий с оператором В подзапрос обычно проще вписать в текст вручную, а затем при необходимости доработать через конструктор.

Как избежать ошибки «подзапрос вернул более одной строки»?

Гарантируйте скалярность: применяйте агрегатные функции МАКСИМУМ, МИНИМУМ, СУММА, КОЛИЧЕСТВО либо условие, однозначно отбирающее одну запись. Если агрегат не подходит по смыслу, перенесите логику в соединение с предварительно сгруппированной временной таблицей — так ошибка исключается структурно.