Использовал ли ты вложенные запросы (подзапросы) в SQL?

«Использовал ли ты вложенные запросы (подзапросы) в SQL?» — вопрос из категории Базы данных, который задают на 28% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, активно использовал вложенные запросы (подзапросы) в различных сценариях. Подзапрос — это запрос, вложенный внутрь другого запроса (SELECT, INSERT, UPDATE, DELETE). Он может возвращать скалярное значение, список значений или целую таблицу.

Основные типы и примеры использования:

  1. Скалярный подзапрос (в SELECT, WHERE, HAVING). Возвращает одно значение.

    -- Найти всех пользователей, чей доход выше среднего по компании
    SELECT name, salary
    FROM employees
    WHERE salary > (SELECT AVG(salary) FROM employees);
  2. Подзапрос, возвращающий множество значений (с IN, ANY, ALL, EXISTS).

    -- Найти товары, которые никогда не заказывались
    SELECT id, name
    FROM products p
    WHERE NOT EXISTS (
        SELECT 1 FROM order_items oi WHERE oi.product_id = p.id
    );
  3. Подзапрос в качестве производной таблицы (в FROM). Результат подзапроса используется как временная таблица.

    -- Найти для каждого отдела его сотрудника с максимальной зарплатой
    SELECT d.name AS department, e.name AS employee, e.salary
    FROM departments d
    JOIN (
        SELECT department_id, MAX(salary) as max_salary
        FROM employees
        GROUP BY department_id
    ) max_sal ON d.id = max_sal.department_id
    JOIN employees e ON e.department_id = max_sal.department_id 
                      AND e.salary = max_sal.max_salary;

Ключевые соображения и опыт:

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

    • Медленно (коррелированный): SELECT * FROM users u WHERE (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) > 5;
    • Быстрее (через JOIN и GROUP BY): SELECT u.* FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(*) > 5;
  • Читаемость: Сложные многоуровневые подзапросы могут сильно ухудшать читаемость SQL. В современных СУБД часто предпочтительнее использовать CTE (Common Table Expressions, WITH clause), которые структурируют запрос и делают его более понятным.

    WITH high_value_orders AS (
        SELECT user_id, SUM(total) as order_sum
        FROM orders
        GROUP BY user_id
        HAVING SUM(total) > 10000
    )
    SELECT u.name, hvo.order_sum
    FROM users u
    JOIN high_value_orders hvo ON u.id = hvo.user_id;
  • Возможности СУБД: Современные оптимизаторы запросов (в PostgreSQL, MySQL 8+, SQL Server) умеют очень хорошо "разворачивать" и оптимизировать подзапросы, иногда преобразуя их в те же JOIN на этапе построения плана выполнения. Всегда стоит проверять план запроса (EXPLAIN) для критичных по производительности операций.

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