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