Что такое подзапрос (subquery)?

Подзапрос (subquery) — это SQL-запрос, вложенный внутрь другого SQL-запроса. Он выполняется отдельно и передаёт своё значение (или набор значений) во внешний запрос. Подзапросы позволяют выполнять сложные выборки, фильтрации, агрегации и сравнения, которые невозможно или неудобно выразить одним простым запросом.

Основные виды подзапросов

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

Пример:

```python
SELECT Name,
(SELECT MAX(Salary) FROM Employees) AS MaxSalary
FROM Employees;

1.  Здесь подзапрос возвращает максимальную зарплату, а внешний запрос добавляет это значение к каждой строке.  


**Множественный подзапрос (множество значений)  
**Возвращает один столбец и несколько строк. Используется с операторами IN, NOT IN, ANY, ALL.  
<br/>**Пример:  
<br/>**```python  
SELECT Name
FROM Employees
WHERE DepartmentID IN (SELECT ID FROM Departments WHERE Location = 'London');  

Коррелированный подзапрос
Выполняется для каждой строки внешнего запроса. Он использует данные из внешнего запроса в своей логике.

Пример:

```python
SELECT Name
FROM Employees E
WHERE Salary > (
SELECT AVG(Salary)
FROM Employees
WHERE DepartmentID = E.DepartmentID
);

1.  Здесь для каждого сотрудника сравнивается его зарплата со средней по его отделу.  


**Подзапрос в FROM (inline view / derived table)  
**Подзапрос работает как временная таблица во внешнем FROM.  
<br/>**Пример:  
<br/>**```python  
SELECT DepartmentName, AvgSalary
FROM (
SELECT DepartmentID, AVG(Salary) AS AvgSalary
FROM Employees
GROUP BY DepartmentID
) AS DeptStats
JOIN Departments ON DeptStats.DepartmentID = [Departments.ID](http://departments.id);  

Подзапрос в SELECT
Можно использовать, чтобы вычислять значения для каждой строки результирующего набора.

Пример:

```python
SELECT Name,
(SELECT COUNT(*) FROM Tasks WHERE Tasks.EmployeeID = Employees.ID) AS TaskCount
FROM Employees;

**Подзапрос в HAVING  
**Используется для фильтрации агрегированных данных.  
<br/>**Пример:  
<br/>**```python  
SELECT DepartmentID, COUNT(\*) AS EmpCount
FROM Employees
GROUP BY DepartmentID
HAVING COUNT(\*) > (
SELECT AVG(EmpCount)
FROM (
SELECT COUNT(\*) AS EmpCount
FROM Employees
GROUP BY DepartmentID
) AS Temp
);  

Операторы, с которыми часто работают подзапросы

IN — проверка принадлежности:

WHERE ID IN (SELECT EmployeeID FROM Timesheets)

EXISTS / NOT EXISTS — проверка наличия строк:

WHERE EXISTS (SELECT 1 FROM Orders WHERE Orders.CustomerID = Customers.ID)

\=, <>, >, <, <=, >= — сравнение с результатом скалярного подзапроса:

WHERE Salary > (SELECT AVG(Salary) FROM Employees)

ALL, ANY, SOME — сравнение с множественными значениями:

WHERE Salary > ALL (SELECT Salary FROM Employees WHERE DepartmentID = 5)

Особенности и производительность

  1. Коррелированные подзапросы
    Могут быть медленными, так как выполняются многократно (по числу строк основного запроса).

  2. Подзапрос в FROM
    Может быть использован как альтернатива временной таблице. Часто — способ разбить сложный запрос на части.

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

Пример подзапроса:

```python
SELECT Name
FROM Employees
WHERE DepartmentID IN (SELECT ID FROM Departments WHERE Location = 'London');

**Эквивалент с JOIN:  
<br/>**```python  
SELECT E.Name
FROM Employees E
JOIN Departments D ON E.DepartmentID = D.ID
WHERE D.Location = 'London';  
  1. Во многих СУБД JOIN выполняется быстрее, чем IN.

  2. **Оптимизация подзапросов
    **

    • Индексируйте поля, используемые во вложенных условиях.

    • Избегайте коррелированных подзапросов, если можно использовать JOIN.

    • Протестируйте альтернативы с EXISTS, IN, JOIN, WITH.

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

  • Когда нужно получить значение, агрегированное из другой таблицы.

  • Для фильтрации по значениям из другой таблицы.

  • Когда использование JOIN усложняет логику или дублирует строки.

  • Для упрощения логики сложных вложенных выборок.

  • При необходимости изолировать подрезультаты (например, использовать агрегат внутри HAVING).

Пример реального сценария

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

SELECT Name
FROM Employees E
WHERE Salary > (
SELECT AVG(Salary)
FROM Employees
WHERE DepartmentID = E.DepartmentID
);

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

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