Что такое подзапрос (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)
Особенности и производительность
-
Коррелированные подзапросы
Могут быть медленными, так как выполняются многократно (по числу строк основного запроса). -
Подзапрос в 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';
-
Во многих СУБД JOIN выполняется быстрее, чем IN.
-
**Оптимизация подзапросов
**-
Индексируйте поля, используемые во вложенных условиях.
-
Избегайте коррелированных подзапросов, если можно использовать JOIN.
-
Протестируйте альтернативы с EXISTS, IN, JOIN, WITH.
-
Когда использовать подзапросы
-
Когда нужно получить значение, агрегированное из другой таблицы.
-
Для фильтрации по значениям из другой таблицы.
-
Когда использование JOIN усложняет логику или дублирует строки.
-
Для упрощения логики сложных вложенных выборок.
-
При необходимости изолировать подрезультаты (например, использовать агрегат внутри HAVING).
Пример реального сценария
Задача: Найти сотрудников, чья зарплата выше средней по их отделу.
SELECT Name
FROM Employees E
WHERE Salary > (
SELECT AVG(Salary)
FROM Employees
WHERE DepartmentID = E.DepartmentID
);
Пояснение: Внутренний подзапрос вычисляет среднюю зарплату по отделу конкретного сотрудника, а внешний запрос сравнивает зарплату сотрудника с этой средней.
Подзапросы — мощный инструмент SQL, который позволяет писать выразительные и гибкие запросы. Они незаменимы, когда нужно выполнять вложенные выборки, работать с агрегированными данными или обрабатывать иерархические структуры.