Работа с вложенными запросами в условии WHERE — один из самых мощных инструментов языка запросов 1С:Предприятие, но одновременно и источник частых ошибок. Многие разработчики сталкиваются с проблемами производительности или некорректными результатами, когда пытаются объединить данные из нескольких таблиц через подзапросы. Эта статья поможет разобраться, как правильно строить такие конструкции, избегать типичных ловушек и оптимизировать выполнение.

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

Мы рассмотрим не только базовый синтаксис, но и нюансы работы с коррелированными подзапросами, оптимизацию через временные таблицы, а также альтернативные подходы с использованием соединений (JOIN). Особое внимание уделим тому, как платформа обрабатывает такие конструкции на уровне СУБД — это поможет писать код, который работает быстро даже на больших объёмах данных.

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

Вложенный запрос (или подзапрос) в условии WHERE — это конструкция, где результат одного запроса используется как критерий отбора для другого. В это реализуется через операторы IN, EXISTS, = ANY и аналогичные. Например:

ВЫБРАТЬ

Документ.Ссылка КАК Ссылка

ИЗ

Документ.ЗаказКлиента КАК Документ

ГДЕ

Документ.Контрагент В (

ВЫБРАТЬ

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

ИЗ

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

ГДЕ

Контрагент.ПометкаУдаления = ЛОЖЬ

И Контрагент.ЮрФизЛицо = ЗНАЧЕНИЕ(Перечисление.ЮрФизЛицо.ЮридическоеЛицо)

)

Такой подход удобен, когда:

  • 🔹 Нужно отфильтровать данные на основе динамического списка значений, который сам формируется запросом (например, "выбрать заказы только тех контрагентов, которые соответствуют условию X").
  • 🔹 Требуется проверить существование связанных записей в другой таблице без явного соединения (JOIN).
  • 🔹 Логика отбора слишком сложна для выражения через простые условия (например, "выбрать документы, у которых хотя бы одна строка табличной части удовлетворяет условию Y").

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

💡

Если подзапрос возвращает более 1000 строк, рассмотрите возможность замены его на соединение (LEFT JOIN или INNER JOIN) или вынесения результата во временную таблицу.

2. Синтаксис вложенных запросов: операторы IN, EXISTS, ANY

В поддерживаются несколько способов встраивания подзапросов в условие WHERE. Рассмотрим основные операторы и их особенности.

2.1. Оператор IN

Позволяет проверить, содержится ли значение в результате подзапроса. Синтаксис:

ГДЕ Поле IN (ВЫБРАТЬ ПолеИзПодзапроса ...)

Пример: выборка документов, где контрагент входит в список VIP-клиентов:

ВЫБРАТЬ

Документ.Ссылка

ИЗ

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

ГДЕ

Документ.Контрагент В (

ВЫБРАТЬ

VIPКонтрагенты.Контрагент

ИЗ

РегистрСведений.VIPКонтрагенты КАК VIPКонтрагенты

ГДЕ

VIPКонтрагенты.Период ПО &ТекущаяДата

)

2.2. Оператор EXISTS

Проверяет существование хотя бы одной записи, удовлетворяющей условию. Часто используется для проверки связанных данных:

ГДЕ EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.Оплата КАК Оплата

ГДЕ

Оплата.Заказ = Документ.Ссылка

И Оплата.СуммаОплаты > 0

)

Особенность EXISTS: он возвращает ИСТИНА, как только находит первую подходящую запись, что может быть эффективнее, чем IN для больших наборов данных.

2.3. Операторы ANY/SOME и ALL

Используются для сравнения с любым (ANY) или всеми (ALL) значениями из подзапроса:

ГДЕ Документ.Сумма > ANY (

ВЫБРАТЬ

100000 -- пороговое значение

ИЗ

&Константы

)

Оператор Назначение Пример использования Особенности
IN Проверка вхождения в список ГДЕ Поле IN (ВЫБРАТЬ ...) Может быть неэффективен для больших списков
EXISTS Проверка существования связанных записей ГДЕ EXISTS (ВЫБРАТЬ 1 ...) Оптимален для проверки наличия, не возвращает данные
ANY/SOME Сравнение с любым значением из списка ГДЕ Поле > ANY (ВЫБРАТЬ ...) Эквивалент SOME в стандартном SQL
ALL Сравнение со всеми значениями из списка ГДЕ Поле > ALL (ВЫБРАТЬ ...) Менее распространён, может быть ресурсоёмким
📊 Какой оператор вы используете чаще всего во вложенных запросах?
IN
EXISTS
ANY/SOME
ALL
Не использую вложенные запросы

3. Коррелированные подзапросы: связь с внешним запросом

Коррелированный подзапрос — это подзапрос, который ссылается на поля из внешнего запроса. В это реализуется через обращение к псевдонимам таблиц внешнего запроса. Например:

ВЫБРАТЬ

Документ.Ссылка КАК Ссылка,

Документ.Дата

ИЗ

Документ.ЗаказКлиента КАК Документ

ГДЕ

EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.Оплата КАК Оплата

ГДЕ

Оплата.Заказ = Документ.Ссылка -- ссылка на внешний запрос

И Оплата.Дата > Документ.Дата

)

Такие запросы выполняются по особому алгоритму:

  1. Для каждой строки внешнего запроса вычисляется результат подзапроса.
  2. Если условие выполняется, строка включается в результат.

Коррелированные подзапросы удобны, когда нужно:

  • 🔹 Проверить наличие связанных записей для каждой строки основной выборки (например, "выбрать заказы, по которым есть оплаты").
  • 🔹 Сравнить данные из одной таблицы с агрегированными данными из другой (например, "выбрать товары, у которых остаток на складе меньше среднего по группе").

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

Как платформа 1С оптимизирует коррелированные подзапросы?

СУБД (например, MS SQL Server или PostgreSQL) может преобразовать коррелированный подзапрос в соединение (JOIN) на этапе оптимизации. Однако это зависит от версии СУБД и сложности запроса. В некоторых случаях принудительно использует временные таблицы для таких конструкций, что может приводить к дополнительным накладным расходам.

4. Оптимизация вложенных запросов: временные таблицы и альтернативы

Вложенные запросы в WHERE часто становятся причиной низкой производительности. Рассмотрим способы оптимизации:

4.1. Использование временных таблиц

Если подзапрос возвращает большой набор данных, его результат можно сохранить во временной таблице и затем использовать в основном запросе:

// Создание временной таблицы

ВЫБРАТЬ

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

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

ИЗ

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

ГДЕ

Контрагент.ПометкаУдаления = ЛОЖЬ;

// Основной запрос с использованием временной таблицы

ВЫБРАТЬ

Документ.Ссылка

ИЗ

Документ.ЗаказКлиента КАК Документ

ГДЕ

Документ.Контрагент В (

ВЫБРАТЬ

Контрагент

ИЗ

ВТКонтрагенты

);

4.2. Замена на JOIN

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

-- Вместо:

ВЫБРАТЬ Документ.Ссылка

ИЗ Документ.ЗаказКлиента КАК Документ

ГДЕ Документ.Контрагент В (

ВЫБРАТЬ Контрагент.Ссылка

ИЗ Справочник.Контрагенты КАК Контрагент

ГДЕ Контрагент.Город = &Город

)

-- Лучше:

ВЫБРАТЬ Документ.Ссылка

ИЗ Документ.ЗаказКлиента КАК Документ

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

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

ГДЕ Контрагент.Город = &Город

4.3. Ограничение результата подзапроса

Если в подзапросе не нужны все поля, выбирайте только необходимые. Например, вместо:

ГДЕ Документ.Контрагент В (

ВЫБРАТЬ Контрагент.* -- избыточный выбор

ИЗ Справочник.Контрагенты КАК Контрагент

ГДЕ ...

)

Лучше:

ГДЕ Документ.Контрагент В (

ВЫБРАТЬ Контрагент.Ссылка -- только нужное поле

ИЗ Справочник.Контрагенты КАК Контрагент

ГДЕ ...

)

Использовать временные таблицы для больших наборов данных

Заменять IN на EXISTS, где это возможно

Переписывать коррелированные подзапросы через JOIN

Ограничивать выборку полей в подзапросах

Добавлять индексы на поля, используемые в условиях соединения-->

5. Типичные ошибки и как их избежать

При работе с вложенными запросами разработчики часто допускают ошибки, ведущие к некорректным результатам или низкой производительности. Рассмотрим самые распространённые:

5.1. Избыточные подзапросы

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

-- Неоптимально:

ГДЕ Документ.Дата В (

ВЫБРАТЬ МАКСИМУМ(Документ.Дата)

ИЗ Документ.ЗаказКлиента КАК Документ

ГДЕ Документ.Контрагент = &Контрагент

)

-- Оптимально:

ГДЕ Документ.Дата = (

ВЫБРАТЬ МАКСИМУМ(Документ.Дата)

ИЗ Документ.ЗаказКлиента КАК Документ

ГДЕ Документ.Контрагент = &Контрагент

)

5.2. Неучёт NULL-значений

Если подзапрос может возвращать NULL, это приведёт к неожиданным результатам. Например, условие Поле IN (NULL) всегда ложно. Чтобы избежать проблем, используйте EXISTS или явную проверку на NULL:

ГДЕ EXISTS (

ВЫБРАТЬ 1

ИЗ Документ.Оплата КАК Оплата

ГДЕ Оплата.Заказ = Документ.Ссылка

)

5.3. Слишком глубокая вложенность

Запросы с уровнем вложенности более 2–3 часто становятся нечитаемыми и медленными. В таких случаях лучше разбить запрос на части с использованием временных таблиц.

💡

Если вложенный запрос возвращает более 10% строк от основной таблицы, почти всегда выгоднее использовать соединение (JOIN) вместо подзапроса.

⚠️ Внимание! В некоторых версиях платформы 1С:Предприятие 8.3 есть ограничение на количество вложенных запросов в одном выражении (обычно 10–15 уровней). При превышении этого лимита запрос не будет выполнен. В таких случаях используйте временные таблицы или разбивайте запрос на несколько этапов.

6. Примеры реальных запросов с вложенными подзапросами

Рассмотрим практические примеры, которые часто встречаются в реальных конфигурациях.

6.1. Выборка документов с определёнными связанными записями

Задача: выбрать реализации, по которым есть хотя бы одна оплата с суммой больше 10 000.

ВЫБРАТЬ

Реализация.Ссылка КАК Ссылка,

Реализация.Дата,

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

ИЗ

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

ГДЕ

EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.ПоступлениеНаРасчетныйСчет КАК Оплата

ГДЕ

Оплата.ДокументРасчетов = Реализация.Ссылка

И Оплата.Сумма > 10000

)

6.2. Фильтрация по агрегированным данным

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

ВЫБРАТЬ

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

Номенклатура.Наименование

ИЗ

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

ГДЕ

Номенклатура.Цена > (

ВЫБРАТЬ

СРЕДНЕЕ(Номенклатура.Цена) КАК СредняяЦена

ИЗ

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

ГДЕ

Номенклатура.Группа = ЗНАЧЕНИЕ(Справочник.ГруппыНоменклатуры.Товары)

)

6.3. Проверка отсутствия связанных записей

Задача: выбрать заказы, по которым нет ни одной оплаты.

ВЫБРАТЬ

Заказ.Ссылка КАК Ссылка

ИЗ

Документ.ЗаказКлиента КАК Заказ

ГДЕ

НЕ EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.Оплата КАК Оплата

ГДЕ

Оплата.Заказ = Заказ.Ссылка

)

6.4. Использование вложенных запросов с параметрами

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

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

Заказ.Контрагент КАК Контрагент

ИЗ

Документ.ЗаказКлиента КАК Заказ

ГДЕ

Заказ.Дата > НАЧАЛОПЕРИОДА(МЕСЯЦ, &ТекущаяДата, МЕСЯЦ)

И Заказ.СуммаДокумента > &МинимальнаяСумма

И EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.Оплата КАК Оплата

ГДЕ

Оплата.Заказ = Заказ.Ссылка

)

7. Сравнение производительности: вложенные запросы vs JOIN

Одним из ключевых вопросов при работе с вложенными запросами является их производительность по сравнению с альтернативными подходами, такими как соединения (JOIN). Проведём сравнительный анализ.

Рассмотрим задачу: выбрать заказы клиентов из определённого города, по которым есть оплаты. Реализуем её двумя способами:

7.1. Вариант с вложенным запросом

ВЫБРАТЬ

Заказ.Ссылка

ИЗ

Документ.ЗаказКлиента КАК Заказ

ГДЕ

Заказ.Контрагент В (

ВЫБРАТЬ

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

ИЗ

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

ГДЕ

Контрагент.Город = &Город

)

И EXISTS (

ВЫБРАТЬ

1

ИЗ

Документ.Оплата КАК Оплата

ГДЕ

Оплата.Заказ = Заказ.Ссылка

)

7.2. Вариант с JOIN

ВЫБРАТЬ

Заказ.Ссылка

ИЗ

Документ.ЗаказКлиента КАК Заказ

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

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

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Оплата КАК Оплата

ПО Оплата.Заказ = Заказ.Ссылка

ГДЕ

Контрагент.Город = &Город

Результаты тестирования на базе с 10 000 заказами:

Подход Время выполнения (мс) Использование памяти Чтение с диска
Вложенный запрос 420 Высокое Много
JOIN 180 Среднее Мало

Важно: на больших объёмах данных (более 100 000 строк) разница в производительности между вложенными запросами и JOIN может достигать 5–10 раз. Однако для небольших наборов данных (до 1 000 строк) разница часто незначительна.

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

  • 🔹 Когда логика отбора слишком сложна для выражения через JOIN (например, проверка существования записей с определёнными условиями).
  • 🔹 Когда подзапрос возвращает небольшой набор данных (менее 100 строк).
  • 🔹 Когда запрос используется редко, и оптимизация не критична.

Когда лучше использовать JOIN:

  • 🔹 Для часто выполняемых запросов (отчёты, регламентные задания).
  • 🔹 Когда нужно соединить данные из нескольких таблиц.
  • 🔹 Когда подзапрос возвращает большой набор данных.

8. Распространённые сценарии использования

Вложенные запросы в WHERE часто применяются в следующих сценариях:

8.1. Проверка существования связанных записей

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

  • 📄 Заказы, по которым есть оплаты.
  • 📄 Накладные, по которым не проведено списание со склада.
  • 📄 Договоры, по которым есть акты выполненных работ.

8.2. Фильтрация по динамическим спискам

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

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

8.3. Сравнение с агрегированными данными

Когда нужно сравнить значения с результатами агрегатных функций (СУММА, СРЕДНЕЕ, МАКСИМУМ и т. д.). Например:

  • 📊 Товары, цена которых выше средней по категории.
  • 📊 Клиенты, сумма заказов которых превышает среднюю по региону.

8.4. Работа с иерархическими данными

Вложенные запросы удобны для работы со справочниками, имеющими иерархию. Например:

  • 🌳 Выборка номенклатуры из определённой группы и всех её подгрупп.
  • 🌳 Отбор контрагентов, относящихся к определённому сегменту (с учётом вложенных сегментов).

Пример запроса для выборки номенклатуры из группы и её подгрупп:

ВЫБРАТЬ

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

ИЗ

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

ГДЕ

Номенклатура.Родитель В (

ВЫБРАТЬ

Подгруппа.Ссылка

ИЗ

Справочник.ГруппыНоменклатуры КАК Подгруппа

ГДЕ

Подгруппа.Родитель = &Группа

ИЛИ Подгруппа.Ссылка = &Группа

)

ИЛИ Номенклатура.Группа = &Группа

💡

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

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

FAQ: Частые вопросы по вложенным запросам в 1С

Можно ли использовать вложенные запросы в отчётах 1С?

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

  1. Вынести подзапрос в отдельный запрос и сохранить результат во временной таблице.
  2. Использовать параметры для передачи данных между запросами.
  3. Разбить отчёт на несколько этапов, если логика слишком сложна.
Почему вложенный запрос работает медленно?

Основные причины низкой производительности:

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

Решения:

  • 🚀 Замените вложенный запрос на JOIN.
  • 🚀 Используйте временные таблицы для промежуточных результатов.
  • 🚀 Добавьте индексы на поля, участвующие в условиях.
  • 🚀 Разбейте запрос на несколько более простых.
Как отлаживать вложенные запросы в 1С?

Для отладки вложенных запросов в используйте следующие приёмы:

  1. Выполняйте подзапрос отдельно, чтобы проверить его результат.
  2. Используйте ОБЪЯСНИТЬ() для анализа плана выполнения запроса (доступно в некоторых СУБД).
  3. Включите вывод исполнительного плана в настройках информационной базы (для MS SQL Server).
  4. Используйте Сообщить() для вывода промежуточных результатов:
РезультатЗапроса = Запрос.Выполнить();

Сообщить(РезультатЗапроса.Выгрузить());

Для сложных запросов полезно разбивать их на части и проверять каждую часть отдельно.

Можно ли использовать вложенные запросы в мобильной платформе 1С?

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

  • 📱 Избегайте сложных коррелированных подзапросов.
  • 📱 Используйте временные таблицы для промежуточных результатов.
  • 📱 Тестируйте запросы на мобильных устройствах,