История вопроса:
Вложенные подзапросы изначально задумывались в SQL для расширения выразительных возможностей языка и решения сложных бизнес-задач, которые не укладываются в простые SELECT-операторы. Однако с ростом объёмов данных и сложностью реляционных моделей пришло понимание — не всегда вложенные подзапросы работают эффективно: многое зависит от реализации оптимизатора конкретной СУБД.
Проблема:
Основной вызов — найти компромисс между читаемостью, правильностью логики и производительностью. Не всегда вложенный подзапрос оптимизируется до join-операций, а часто превращается в дорогостоящие циклы перебора (Nested Loops).
Решение:
Пример кода:
-- Вложенный подзапрос в SELECT (аккуратно!) SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.client_id = clients.id) AS order_count FROM clients; -- Эквивалент через JOIN (обычно быстрее): SELECT clients.name, COUNT(orders.id) AS order_count FROM clients LEFT JOIN orders ON orders.client_id = clients.id GROUP BY clients.name;
Ключевые особенности:
Работает ли вложенный подзапрос в SELECT быстрее, чем аналогичный LEFT JOIN?
Чаще — нет. Коррелированный подзапрос в SELECT исполняется для каждой строки внешнего запроса, тогда как JOIN строится один раз с индексами для всей таблицы.
Можно ли использовать подзапрос в FROM вместо CTE (WITH), и будет ли разница?
Да, подзапрос в FROM:**
SELECT t1.id, sub.agg FROM table1 t1 JOIN (SELECT id, MAX(val) AS agg FROM table2 GROUP BY id) sub ON t1.id = sub.id;
Но CTE иногда обладает большей читаемостью и может приводить к другой оптимизации в планах выполнения.
Все вложенные подзапросы оптимизируются до аналогичных JOIN?
Нет. Не все СУБД умеют это делать одинаково, иногда вложенный подзапрос приводит к сканированию каждой строки, особенно если есть корреляция между внешним и внутренним запросом.
Менеджер продаж сделал отчёт по клиентам, считая внутренним SELECT количество заказов. Времена выполнения — минуты, нагрузка на сервер росла в геометрической прогрессии.
Плюсы:
Запрос переписали с LEFT JOIN и группировкой.
Плюсы: