SELECT — это «покажи мне данные»: SELECT что FROM откуда WHERE при каком условии ORDER BY как отсортировать LIMIT сколько строк. JOIN склеивает две таблицы по ключу (помните PK/FK из записи про базы) — потому что данные лежат раздельно: клиенты в одной таблице, заказы в другой. INNER JOIN оставляет только строки, у которых есть пара в обеих таблицах; LEFT JOIN — все строки слева плюс совпадения справа (а где совпадения нет — пусто). GROUP BY с COUNT/SUM схлопывает строки в итоги: сколько заказов у клиента, на какую сумму. Главная ловушка — забыть условие склейки: тогда база перемножит таблицы и вернёт мусор.
Меня долго смущало, что аналитику вообще нужен SQL — есть же разработчики. А потом я месяц подряд бегал к бэкендеру с вопросами «а сколько у нас заказов без оплаты», «а правда, что у этих клиентов нет телефона», «покажи, что реально лежит в этом поле». Каждый раз он отвлекался, я ждал полдня. Когда я научился писать пять строк SELECT сам — эти полдня превратились в минуту, а заодно я перестал придумывать требования, которые бьются о реальные данные.
Аналитик читает и пишет SQL не для того, чтобы заменить разработчика. Три задачи закрывают 90% случаев: проверить данные (что на самом деле лежит в таблице, а не что написано в документации), понять структуру (как связаны сущности, есть ли дубли) и собрать выборку для отчёта (сколько, на какую сумму, за какой период). Для всего этого хватает SELECT, JOIN и GROUP BY.
Анатомия SELECT
SELECT — это запрос на чтение. Он ничего не меняет в базе, поэтому его не страшно запускать: максимум вернёт не то, что вы хотели. Запрос читается почти как английская фраза, и порядок частей всегда один:
SELECT name, phone -- что показать (какие столбцы)
FROM clients -- откуда взять (из какой таблицы)
WHERE city = 'Москва' -- при каком условии (фильтр строк)
ORDER BY name -- как отсортировать
LIMIT 100; -- сколько строк вернуть
Разберём по частям. SELECT — какие столбцы нужны; звёздочка (SELECT *) означает «все столбцы», удобно для разведки, но в отчётах лучше перечислять явно. FROM — из какой таблицы. WHERE — условие, которое оставляет только нужные строки (город «Москва», сумма больше тысячи, дата за прошлый месяц). ORDER BY — сортировка, по возрастанию или с припиской DESC по убыванию. LIMIT — ограничение на число строк, чтобы не вытащить миллион записей случайно.
Привычка, которая бережёт нервы
Когда разбираете незнакомую таблицу, начинайте с SELECT * FROM таблица LIMIT 10. Это покажет реальные столбцы и реальные значения за секунду — часто оказывается, что поле, которое в документации называется «статус», на деле хранит коды вроде 0/1/2, а не слова. Источник истины — данные, а не их описание.
Зачем нужен JOIN
В нормализованной базе данные лежат раздельно: имя и телефон клиента — в таблице clients, его заказы — в таблице orders, а связаны они ключом. Если вы не помните, почему так, — это разобрано в записи про базы данных, ключи и нормализацию: в заказе хранится не имя клиента, а только client_id — внешний ключ (FK), ссылка на первичный ключ (PK) в clients.
Это экономно, но создаёт задачу: чтобы показать «заказ №123 и имя того, кто его сделал», надо взять строку из orders, прочитать её client_id и найти строку с таким id в clients. Вот это «найти пару в другой таблице по ключу» и делает JOIN — склеивает строки двух таблиц там, где ключи совпадают.
SELECT orders.id, orders.created_at, clients.name, clients.phone
FROM orders
JOIN clients ON orders.client_id = clients.id;
Ключевая часть — ON orders.client_id = clients.id. Это условие склейки: «соединяй строку заказа со строкой клиента тогда, когда client_id заказа равен id клиента». Без этого условия JOIN не знает, какую строку с какой соединять.
INNER и LEFT: два вида склейки, которые нужны на практике
Видов JOIN несколько, но в работе аналитика реально нужны два. Разница между ними — что делать со строками, у которых нет пары.
INNER JOIN (или просто JOIN) оставляет только те строки, у которых пара нашлась в обеих таблицах. Клиент без единого заказа в результат не попадёт — у него нет ни одной строки в orders, склеивать не с чем. Это пересечение: только то, что есть и там, и там.
LEFT JOIN оставляет все строки левой таблицы (той, что в FROM), а из правой подставляет совпадения; где совпадения нет — ставит пусто (NULL). Клиент без заказов попадёт в результат, просто поля заказа у него будут пустыми.
-- Покажи клиентов и их количество заказов.
-- LEFT JOIN, потому что клиенты без заказов нам тоже важны.
SELECT clients.name, orders.id AS order_id
FROM clients
LEFT JOIN orders ON orders.client_id = clients.id;
Зачем это на практике? Самый частый бизнес-вопрос — «найди тех, у кого чего-то нет»: клиенты без заказов (не покупают — почему?), заказы без оплаты (зависли), товары без продаж. INNER JOIN такие строки выбросит и вы их не увидите. LEFT JOIN их сохранит, а дальше можно отфильтровать WHERE orders.id IS NULL — «оставь только тех клиентов, кому ни один заказ не подобрался». Именно поэтому путать INNER и LEFT опасно: на INNER отчёт «молча» теряет часть данных, и ошибку легко не заметить.
Лево и право — это про порядок в запросе
«Левая» таблица — та, что стоит в FROM, «правая» — та, что после JOIN. LEFT JOIN бережёт все строки левой. Есть и RIGHT JOIN (бережёт правую), но его почти не используют: проще поменять таблицы местами и написать привычный LEFT. Так что на практике запоминаете два: INNER — только совпадения, LEFT — все слева плюс совпадения.
GROUP BY: от строк к итогам
Пока мы доставали отдельные строки. Но бизнес чаще спрашивает не «покажи заказы», а «сколько заказов у каждого клиента и на какую сумму». Это значит — сгруппировать строки и посчитать итог по каждой группе. Для этого есть GROUP BY и функции-агрегаты, которые схлопывают много строк в одно число:
- COUNT(*) — сколько строк (заказов, клиентов).
- SUM(поле) — сумма значений (общая выручка).
- Реже: AVG (среднее), MIN/MAX (минимум и максимум).
SELECT clients.name,
COUNT(orders.id) AS orders_count,
SUM(orders.amount) AS total_spent
FROM clients
LEFT JOIN orders ON orders.client_id = clients.id
GROUP BY clients.name
ORDER BY total_spent DESC;
Читается так: склеили клиентов с их заказами, потом GROUP BY clients.name собрал все строки одного клиента в одну группу, а COUNT и SUM посчитали по этой группе число заказов и потраченную сумму. ORDER BY ... DESC поставил наверх тех, кто принёс больше всего денег. Правило, которое экономит время: если в SELECT есть и обычные столбцы, и агрегаты, — обычные столбцы должны быть в GROUP BY.
Типовая ошибка: забыл условие склейки
Самая частая и самая коварная ошибка новичка — JOIN без ON (или с неправильным условием). База в этом случае не падает с ошибкой — она послушно соединяет каждую строку левой таблицы с каждой строкой правой. Это называется декартовым произведением: 1000 клиентов × 5000 заказов = 5 миллионов строк бессмысленного мусора.
-- ОПАСНО: нет ON, база перемножит таблицы
SELECT * FROM clients, orders;
-- ПРАВИЛЬНО: есть условие склейки
SELECT * FROM clients JOIN orders ON orders.client_id = clients.id;
Коварство в том, что запрос отрабатывает и возвращает данные — просто их слишком много и они неверные. Если ваш отчёт внезапно показывает в разы больше строк или сумму выручки больше, чем вся выручка компании за всю историю, — первое, что проверяю: не потерялось ли условие JOIN. Сумма «раздувается», потому что каждая строка задвоилась-задесятерилась на лишних склейках.
Запускайте только SELECT
SELECT безопасен — он читает, не меняя. А вот UPDATE и DELETE меняют данные, и DELETE без WHERE сотрёт всю таблицу. Если вам дали доступ к боевой базе для проверки данных — пишите только SELECT и никогда не запускайте UPDATE/DELETE «посмотреть, что будет». Для разведки данных просите доступ только на чтение (read-only) — это нормальная и правильная просьба.
Откуда это взялось
В основе SQL — реляционная модель Эдгара Кодда (IBM, 1970): данные как таблицы, связанные по значениям, а не по физическим указателям. Чтобы с этой моделью можно было работать словами, а не математикой, в IBM в проекте System R в середине 1970-х придумали язык SEQUEL — позже сокращённый до SQL. Идея «опиши, ЧТО хочешь получить, а КАК достать — забота базы» оказалась настолько удачной, что SQL пережил полвека и десятки сменившихся технологий. JOIN — прямое отражение кодовской идеи: таблицы существуют раздельно, а связываются в момент запроса.
Частые вопросы
Что такое JOIN в SQL простыми словами?
JOIN склеивает строки двух таблиц по совпадению ключа. Данные в базе лежат раздельно (клиенты отдельно, заказы отдельно), а в заказе хранится только client_id — ссылка на клиента. JOIN по условию ON orders.client_id = clients.id находит для каждого заказа его клиента и соединяет строки, чтобы в одном результате были и заказ, и имя того, кто его сделал.
Чем INNER JOIN отличается от LEFT JOIN?
INNER JOIN оставляет только строки, у которых нашлась пара в обеих таблицах (клиент без заказов в результат не попадёт). LEFT JOIN оставляет все строки левой таблицы плюс совпадения из правой, а где совпадения нет — ставит пусто (NULL). LEFT нужен, когда важно не потерять строки без пары — например, найти клиентов без единого заказа через LEFT JOIN и фильтр WHERE orders.id IS NULL.
Зачем нужен GROUP BY?
GROUP BY собирает строки в группы по значению столбца, чтобы посчитать итог по каждой группе функциями-агрегатами: COUNT (сколько строк), SUM (сумма), AVG (среднее). Например, GROUP BY client_id с COUNT даёт число заказов у каждого клиента, а с SUM(amount) — сколько каждый потратил. Правило: обычные столбцы из SELECT, не обёрнутые в агрегат, должны присутствовать в GROUP BY.