Подзапросы
Учимся вкладывать один запрос внутрь другого.
Подзапрос — это
SELECT, вложенный внутрь другого запроса; его результат используется внешним запросом.
Подзапрос в WHERE с IN
Иногда условие зависит от другой таблицы. Например, «выбрать клиентов, у которых был крупный заказ». Сначала подзапрос находит id таких клиентов, а внешний запрос выбирает их имена.
CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);
INSERT INTO customers (id, name) VALUES (1, 'Аня'), (2, 'Борис'), (3, 'Вера');
INSERT INTO orders (customer_id, amount) VALUES (1, 500), (1, 1200), (2, 800);
SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE amount > 1000);
Вывод:
Аня
Подзапрос вернул id клиентов с заказом дороже 1000 (только id = 1), а внешний запрос показал их имена.
Скалярный подзапрос в сравнении
Если подзапрос возвращает одно значение, его можно сравнивать напрямую. Найдём товары дороже средней цены — средняя считается подзапросом:
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER);
INSERT INTO products (name, price) VALUES
('Клавиатура', 2500), ('Мышь', 1200), ('Монитор', 18000);
SELECT name, price FROM products
WHERE price > (SELECT AVG(price) FROM products);
Вывод:
Монитор|18000
Средняя цена ≈ 7233, и только «Монитор» её превышает. Подзапрос (SELECT AVG(price) ...) вернул одно число, с которым мы сравниваем.
NOT IN — кого нет в списке
Зеркальная задача: найти клиентов без заказов. Подзапрос даёт id всех, у кого заказы есть, а NOT IN оставляет остальных.
CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER);
INSERT INTO customers (id, name) VALUES (1, 'Аня'), (2, 'Борис'), (3, 'Вера');
INSERT INTO orders (customer_id) VALUES (1), (2);
SELECT name FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);
Вывод:
Вера
Внимание: если столбец в подзапросе может содержать NULL, NOT IN ведёт себя коварно (из-за логики NULL) и может вернуть пусто. В таких случаях надёжнее NOT EXISTS или LEFT JOIN ... WHERE ... IS NULL.
Подзапрос в списке выбора
Подзапрос можно поставить и в сам SELECT, посчитав значение для каждой строки. Например, число заказов каждого клиента:
CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER);
INSERT INTO customers (id, name) VALUES (1, 'Аня'), (2, 'Борис'), (3, 'Вера');
INSERT INTO orders (customer_id) VALUES (1), (1), (2);
SELECT name,
(SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS orders_count
FROM customers c
ORDER BY name;
Вывод:
Аня|2 Борис|1 Вера|0
Часто ту же задачу решают через JOIN + GROUP BY (следующий раздел) — это бывает эффективнее, но подзапрос нагляднее для понимания.
Итог
- Подзапрос — это
SELECTвнутри запроса; его результат использует внешний запрос. IN/NOT INс подзапросом фильтруют по списку значений из другой таблицы.- Скалярный подзапрос (одно значение) сравнивают напрямую; подзапрос можно ставить и в список
SELECT.