← Назад к вопросам

Что такое вложенные запросы?

1.3 Junior🔥 241 комментариев
#Запросы и оптимизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Вложенные запросы в 1С

Вложенные запросы (subqueries) — это мощный механизм в SQL, который позволяет встраивать один запрос внутри другого. В 1С вложенные запросы используются для реализации сложной логики выборки данных, фильтрации и аналитики. Это критически важный инструмент для опытного разработчика.

Что это такое

Вложенный запрос — это SELECT внутри другого SELECT, WHERE, FROM или HAVING:

// Синтаксис
СЕЛЕКТ ... ИЗ (
    ВЫБРАТЬ ... ИЗ Таблица // Вложенный запрос
) КАК АльиасТаблицы
ГДЕ ...

Типы вложенных запросов

1. Вложенный SELECT в FROM (подзапрос как источник)

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

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  ПроизводРеестр.Товар,
    |  ПроизводРеестр.ОбщееКоличество
    |ИЗ
    |  (
    |    ВЫБРАТЬ  // Вложенный запрос
    |      Товары.Ссылка КАК Товар,
    |      СУММА(НаличиеТоваров.Количество) КАК ОбщееКоличество
    |    ИЗ
    |      РегистрНакопления.НаличиеТоваров КАК НаличиеТоваров
    |      ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Товары КАК Товары
    |        НА НаличиеТоваров.Товар = Товары.Ссылка
    |    ГРУППИРОВАТЬ ПО
    |      Товары.Ссылка
    |    ИМЕЮЩЕЕ
    |      СУММА(НаличиеТоваров.Количество) > 0
    |  ) КАК ПроизводРеестр
    |ПОРЯДОК ПО
    |  ПроизводРеестр.ОбщееКоличество УБЫВ");

Результат = Запрос.Выполнить();

Когда использовать:

  • Нужны предварительные вычисления (промежуточные суммы)
  • Сложная логика фильтрации
  • Несколько этапов преобразования данных
  • Нужны промежуточные GROUP BY

Преимущества:

  • Читаемость (каждый уровень отвечает за свою логику)
  • Переиспользование логики
  • Лучшая производительность (БД оптимизирует поэтапно)

2. Вложенный SELECT в WHERE (фильтрация по результатам)

Пример: Найти товары, которые продаются лучше среднего

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  Товары.Наименование,
    |  Продажи.КоличествоПроданного
    |ИЗ
    |  РегистрНакопления.ПродажиТоваров КАК Продажи
    |  ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Товары КАК Товары
    |    НА Продажи.Товар = Товары.Ссылка
    |ГДЕ
    |  Продажи.КоличествоПроданного > (
    |    ВЫБРАТЬ
    |      СРЕДНЕЕ(Продажи2.КоличествоПроданного) // Подзапрос
    |    ИЗ
    |      РегистрНакопления.ПродажиТоваров КАК Продажи2
    |  )
    |ПОРЯДОК ПО
    |  Продажи.КоличествоПроданного УБЫВ");

Когда использовать:

  • Нужно сравнить значение с результатом вычисления
  • Фильтрация по условиям из другой таблицы
  • Поиск аномалий или выбросов

Проблема — производительность:

// ❌ ПЛОХО — может быть очень медленно
ГДЕ Товар В (
    ВЫБРАТЬ Товар ИЗ РегистрНакопления.Продажи
    ГДЕ Количество > 100
)

// ✅ ХОРОШО — используй ВНУТРЕННЕЕ СОЕДИНЕНИЕ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (...) НА ...

3. Вложенный SELECT с EXISTS (проверка существования)

Пример: Найти клиентов, которые сделали хотя бы одну покупку

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  Клиенты.Наименование
    |ИЗ
    |  Справочник.Клиенты КАК Клиенты
    |ГДЕ
    |  СУЩЕСТВУЕТ ( // EXISTS в 1С
    |    ВЫБРАТЬ 1 ИЗ Документ.ЗаказКлиента КАК Заказы
    |    ГДЕ Заказы.Клиент = Клиенты.Ссылка
    |  )");

Когда использовать:

  • Проверка наличия связанных записей
  • Поиск главных записей, у которых есть детали
  • Фильтрация по наличию документов

Производительность: EXISTS обычно быстрее IN, потому что БД может остановиться после нахождения первого совпадения.

4. Рекурсивные запросы (WITH RECURSIVE)

Пример: Получить всю иерархию подразделений

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  Подразделения.Ссылка КАК Подразделение,
    |  Подразделения.Наименование,
    |  Подразделения.Родитель,
    |  0 КАК ГлубинаИерархии
    |ИЗ
    |  Справочник.Подразделения КАК Подразделения
    |ГДЕ
    |  Подразделения.Родитель = &КорневоеПодразделение // Начало
    |
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    |  Подразделения.Ссылка,
    |  Подразделения.Наименование,
    |  Подразделения.Родитель,
    |  РеквРезультат.ГлубинаИерархии + 1
    |ИЗ
    |  Справочник.Подразделения КАК Подразделения
    |  ВНУТРЕННЕЕ СОЕДИНЕНИЕ (/* уже построенное дерево */) КАК РеквРезультат
    |    НА Подразделения.Родитель = РеквРезультат.Подразделение");

Когда использовать:

  • Иерархические структуры (org chart, категории)
  • Обход графа (кто подчиняется кому)
  • Все уровни отношений

Практические примеры из реальной работы

Пример 1: Финансовая аналитика

// Найти товары, у которых маржа выше средней по категории

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  Продажи.Товар,
    |  Продажи.Маржа
    |ИЗ
    |  (
    |    ВЫБРАТЬ
    |      РеестрПродаж.Товар,
    |      Товары.Категория,
    |      (РеестрПродаж.ЦенаПродажи - РеестрПродаж.СебестоимостьПродажи) / 
    |        РеестрПродаж.ЦенаПродажи * 100 КАК Маржа
    |    ИЗ
    |      РегистрСведений.ЦеныИСебестоимость КАК РеестрПродаж
    |      ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Товары КАК Товары
    |        НА РеестрПродаж.Товар = Товары.Ссылка
    |  ) КАК Продажи
    |ГДЕ
    |  Продажи.Маржа > (
    |    ВЫБРАТЬ
    |      СРЕДНЕЕ(МаржаПомощь.Маржа)
    |    ИЗ
    |      (...) КАК МаржаПомощь
    |    ГДЕ
    |      МаржаПомощь.Категория = Продажи.Категория
    |  )");

Пример 2: Отслеживание изменений

// Получить последнее изменение для каждого товара

Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |  История.Товар,
    |  История.ОтДаты,
    |  История.Цена
    |ИЗ
    |  (
    |    ВЫБРАТЬ
    |      Цены.Товар,
    |      Цены.ОтДаты,
    |      Цены.Цена,
    |      РАНГ() ПО (РАЗДЕЛ ПО Цены.Товар ПОРЯДОК ПО 
    |        Цены.ОтДаты УБЫВ) КАК РангЗаписи
    |    ИЗ
    |      РегистрСведений.ИсторияЦен КАК Цены
    |  ) КАК История
    |ГДЕ
    |  История.РангЗаписи = 1");

Оптимизация вложенных запросов

Правило 1: Используй ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо IN

// ❌ Медленно
ГДЕ Товар В (
    ВЫБРАТЬ Товар ИЗ РегистрНакопления.Продажи
)

// ✅ Быстро
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи НА Основной.Товар = Продажи.Товар

Правило 2: Кэшируй подзапросы если выполняются много раз

// Если подзапрос одинаковый для всех строк
СредняяЦена = ПолучитьЗначение(
    Новый Запрос("ВЫБРАТЬ СРЕДНЕЕ(Цена) ИЗ РегистрСведений.Цены")
).ПолучитьЗначение();

// Затем используй переменную в основном запросе
Запрос.УстановитьПараметр("СредняяЦена", СредняяЦена);

Правило 3: Индексируй поля в подзапросах

  • Все поля в ON в JOIN должны быть индексированы
  • Поля в WHERE подзапросов должны быть индексированы
  • GROUP BY поля должны быть индексированы

Типичные ошибки

  1. Много вложенности — > 3 уровней вложения тяжело читать
  2. Подзапрос в SELECT вместо GROUP BY — считает для каждой строки
  3. Забыли JOIN — используют IN где нужен JOIN
  4. Циклические зависимости — в рекурсивных запросах
  5. NULL значения — в подзапросах могут быть NULL, забыли про это

Инструменты отладки

// Вывести SQL запрос для отладки
Сообщить(Запрос.GetQueryText());

// Профилирование в IDE
// Запросы → Анализ запросов → Выполнить с профилированием

Заключение

Вложенные запросы — это мощный инструмент для сложной аналитики и отчётности. Они требуют:

  • Понимания SQL синтаксиса
  • Умения оптимизировать для производительности
  • Знания индексирования
  • Навыков тестирования

Профессиональное владение вложенными запросами отличает опытного 1С разработчика от новичка.