SQL для системного аналитика с нуля: SELECT, JOIN, агрегаты
Меня ни разу не спрашивали на собеседовании: «Расскажите про третью нормальную форму». Но «напишите запрос, который достанет список клиентов с суммой их заказов за последний месяц» — спрашивали всегда. SQL для аналитика — не про администрирование баз и не про настройку репликации. Это инструмент добычи данных: вы пишете запрос, база отдаёт таблицу, вы делаете выводы.
Если вы начинающий аналитик — стартовый roadmap поможет понять общую картину профессии. Здесь же — только SQL. Ровно тот минимум, который нужен, чтобы пройти техническую часть собеса и начать работать с реальными данными.
Что такое SELECT и почему вы будете писать его каждый день
SELECT — ключевое слово SQL, которое говорит базе: «верни мне данные». Это не просто «покажи таблицу», а полноценный конвейер: источник → фильтр → группировка → сортировка → порция.
Самый простой запрос выглядит так:
SELECT name, emailFROM clients;Перевод: «из таблицы clients покажи колонки name и email». Звёздочка * вместо списка колонок означает «все колонки». На проде так лучше не делать — вытянете гигабайт blob’ов и положите базу.
Первое, с чем сталкивается новичок: SQL-запросы пишутся не в том порядке, в котором выполняются. Вы пишете SELECT ... FROM ... WHERE ..., а база выполняет: сначала FROM, потом WHERE, потом GROUP BY, потом HAVING, потом SELECT, потом ORDER BY, потом LIMIT. Это важно — иначе непонятно, почему WHERE не видит алиасы из SELECT.
WHERE: фильтруем до того, как получили результат
WHERE отбрасывает строки, которые не проходят условие. Пишется до группировки.
SELECT name, created_atFROM ordersWHERE status = 'paid' AND created_at >= '2026-01-01';Этот запрос достанет оплаченные заказы с начала года. Условия соединяются через AND, OR, NOT. Скобки работают как в математике:
WHERE (status = 'paid' OR status = 'shipped') AND amount > 1000Пара нюансов, на которых сыплются:
=а не==— SQL использует одиночное равно для сравнения.- Строки в одинарных кавычках —
'paid', не"paid". Двойные кавычки в SQL — для имён таблиц и колонок. NULLпроверяется черезIS NULL, а не= NULL—NULLв SQL означает «неизвестно», и любое сравнение сNULLдаётNULL, а неTRUEилиFALSE. ПоэтомуWHERE column = NULLне найдёт ни одной строки. Правильно:WHERE column IS NULLилиWHERE column IS NOT NULL.
JOIN: зачем аналитику соединять таблицы
Данные в нормальной базе разнесены по таблицам. Заказы — в orders, клиенты — в clients, товары — в products. Чтобы ответить на вопрос «клиенты из Москвы, потратившие больше 100 000 за квартал», нужно соединить минимум две таблицы. Для этого — JOIN.
SELECT c.name, o.amount, o.created_atFROM orders oJOIN clients c ON o.client_id = c.idWHERE c.city = 'Москва';Что здесь происходит: для каждой строки из orders база ищет строку в clients с таким же id. Алиасы o и c — короткие псевдонимы таблиц, чтобы не писать полные имена каждый раз. ON задаёт условие соединения — какой столбец с каким сравнивать.
Типов JOIN четыре, но на практике хватает трёх:
На диаграмме три JOIN-а на одних и тех же данных. В orders три заказа (id клиентов 1, 2, 3), в clients три клиента (id 1, 2, 4). INNER JOIN вернул только строки, где id совпали в обеих таблицах — заказы клиентов 1 и 2. Заказ клиента 3 (его нет в clients) и клиент 4 (без заказов) выпали. LEFT JOIN сохранил все строки из левой таблицы orders — для клиента 3 подставил NULL в колонках из clients. FULL JOIN собрал вообще всё, заполнив несовпадения NULL-ами с обеих сторон.
Правило простое: если вам нужны все заказы, даже те, у которых нет клиента (например, при миграции данных) — LEFT JOIN. Если только заказы существующих клиентов — INNER JOIN. RIGHT JOIN — по сути LEFT JOIN наоборот, я за всю практику использовал его раза три.
GROUP BY и агрегатные функции: от строк к цифрам
Аналитику редко нужны сырые строки. Обычно нужно «средний чек по месяцам», «количество заказов на клиента», «сумма продаж по категориям». Для этого — GROUP BY и агрегатные функции.
Основные агрегаты:
| Функция | Что делает | Пример |
|---|---|---|
COUNT(*) | Количество строк | COUNT(*) → 142 |
SUM(column) | Сумма значений | SUM(amount) → 847 200 |
AVG(column) | Среднее | AVG(amount) → 5 966 |
MIN(column) | Минимум | MIN(created_at) → самая ранняя дата |
MAX(column) | Максимум | MAX(amount) → самый дорогой заказ |
GROUP BY собирает строки в группы по значению одной или нескольких колонок, и агрегаты считаются внутри каждой группы:
SELECT client_id, COUNT(*) AS order_count, SUM(amount) AS totalFROM ordersWHERE status = 'paid'GROUP BY client_idORDER BY total DESC;Этот запрос для каждого клиента считает количество оплаченных заказов и общую сумму, сортирует по убыванию суммы. AS задаёт алиас для колонки в результате — в коде приложения вы получите поле total, а не sum(amount).
Важно: в SELECT можно класть только те колонки, которые есть в GROUP BY, плюс агрегаты. Запрос SELECT client_id, name, COUNT(*) ... GROUP BY client_id упадёт, если name не в GROUP BY. Логика простая: в группе из десяти строк у client_id одно значение, а у name их может быть десять — что показывать?
HAVING: фильтр после группировки
WHERE фильтрует строки до группировки. А если нужно отбросить группы, где сумма меньше порога? Для этого HAVING:
SELECT client_id, COUNT(*) AS orders, SUM(amount) AS totalFROM ordersGROUP BY client_idHAVING SUM(amount) > 100000ORDER BY total DESC;Здесь база сначала группирует все заказы по клиентам, потом HAVING отсекает группы с суммой меньше 100 000, и только потом сортирует. WHERE такое не сможет — на этапе WHERE агрегатов ещё не существует.
Частая ошибка: писать WHERE SUM(amount) > 100000. Не работает. WHERE — до агрегации, HAVING — после. Мысленно проговорите порядок выполнения, и ошибка станет очевидной.
Анатомия SQL-запроса: что, зачем и в каком порядке
Одна диаграмма вместо тысячи слов. Порядок, в котором база обрабатывает запрос:
На схеме видно главное: запрос идёт не сверху вниз по тексту, а по конвейеру. Сначала база берёт таблицы (FROM), соединяет (JOIN), фильтрует строки (WHERE), группирует (GROUP BY), фильтрует группы (HAVING), выбирает колонки (SELECT), сортирует (ORDER BY) и отрезает порцию (LIMIT). Ромбы — это необязательные шаги: если нет JOIN — пропускаем, нет GROUP BY — идём сразу к SELECT.
Держите эту схему перед глазами, когда пишете запрос сложнее чем SELECT * FROM. Она спасает от 90% ошибок с алиасами и фильтрацией.
Как это выглядит на реальной задаче
Представьте: продакт просит «дай топ-10 клиентов по сумме заказов за последний квартал, только оплаченные». Вы открываете DBeaver и пишете:
SELECT c.name, COUNT(o.id) AS order_cnt, SUM(o.amount) AS totalFROM orders oJOIN clients c ON o.client_id = c.idWHERE o.status = 'paid' AND o.created_at >= '2026-07-01' AND o.created_at < '2026-10-01'GROUP BY c.nameORDER BY total DESCLIMIT 10;Разбор по шагам: FROM orders o — основная таблица, JOIN clients c — подтягиваем имена клиентов, WHERE — только оплаченные и только за Q3, GROUP BY c.name — группируем по клиенту, SELECT — считаем количество заказов и сумму, ORDER BY total DESC — сортируем от больших к малым, LIMIT 10 — берём десятку.
Результат — таблица из трёх колонок. В Excel или Python-ноутбуке дальше можно строить график. Но основа — вот этот запрос.
Типичные ошибки начинающего аналитика
Три ошибки, которые я вижу на каждом втором код-ревью.
Ошибка 1: COUNT(column) вместо COUNT(*). COUNT(column) считает только NOT NULL значения. Если в колонке email у кого-то NULL, вы получите число меньше реального. COUNT(*) считает строки — для аналитики обычно нужно именно оно.
Ошибка 2: фильтрация по агрегату через WHERE. Уже упоминал, но повторю — это настолько частая ошибка, что стоит отдельного напоминания. WHERE SUM(amount) > 1000 не сработает. Нужен HAVING.
Ошибка 3: JOIN без условия или с неправильным условием. Забыли ON — получили декартово произведение: каждая строка левой таблицы соединилась с каждой строкой правой. Там, где ждали 200 строк, прилетело 40 000 и база упала по таймауту.
Отдельная история — подзапросы. Их часто пишут там, где достаточно JOIN или GROUP BY. Прежде чем писать SELECT ... FROM (SELECT ...), спросите себя: можно ли решить задачу одним запросом с GROUP BY? В восьми случаях из десяти — можно.
Что дальше
SQL — не про заучивание синтаксиса. Он про понимание того, как данные лежат в таблицах и как их преобразовывать. Если вы усвоили SELECT, JOIN, GROUP BY и HAVING — техническая часть собеса на позицию джуна-аналитика у вас в кармане.
База для тренировки: возьмите открытый датасет (продажи, фильмы, авиаперевозки — что угодно), загрузите в SQLite и пробуйте. Не смотрите на чужие запросы первые полчаса. Сами. Это единственный способ набить руку.
Когда базовый SQL перестанет быть узким местом — возвращайтесь к теме с другой стороны: как AI генерирует SQL-запросы и помогает проектировать схемы. LLM уже неплохо пишут SELECT и JOIN, но понимать, что именно они сгенерировали, всё равно придётся вам.
Чек-лист для аналитика
- Пишете
SELECTс явным списком колонок, а не* - Понимаете разницу между
INNER JOIN,LEFT JOINи когда что применять - Используете
WHEREдля фильтрации строк иHAVINGдля фильтрации групп - Не путаете порядок выполнения (
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT) - Умеете написать запрос с
GROUP BY,COUNT,SUM,AVGи объяснить результат - Знаете, что
COUNT(column)не равенCOUNT(*), и почему - Проверяете план запроса, если он выполняется дольше пары секунд