SQL с нуля: как работают SELECT, FROM и WHERE
Разбираем первый SQL-запрос на понятном примере: как выбрать колонки, указать таблицу, отфильтровать строки и не перепутать результат с данными продукта.
Содержание статьи
Первый SQL-запрос лучше учить не на абстрактных цифрах, а на вопросе из реальной работы: “Покажи пользователей, которые пришли из organic в июне”. В нём уже есть всё необходимое — какие поля нужны, из какой таблицы их взять и какие строки оставить. Разберём эту логику на простом примере и закончим запросом, который можно выполнить в бесплатном тренажёре КейсПрактики.
Коротко
У базового запроса есть три главные детали: SELECT выбирает столбцы в результате, FROM указывает источник данных, а WHERE отбрасывает строки, которые не подходят под условие. Порядок написания всегда такой: сначала что показать, затем откуда взять, затем что оставить.
SELECT— какие поля или расчёты вернуть.FROM— из какой таблицы или представления читать данные.WHERE— какие строки подходят под вопрос.- Перед запросом нужно понять, что означает одна строка в таблице.
- Для предсказуемой выборки добавляй
ORDER BY, а для короткого примера —LIMIT.
Сначала разберись со структурой данных
Представим учебную таблицу users. В ней одна строка — один пользователь. Это важно зафиксировать до запроса: если одна строка уже описывает пользователя, можно читать список пользователей напрямую. В таблице events одна строка, наоборот, обычно означает одно событие, поэтому подсчёт строк там не равен подсчёту людей.
В тренажёре SQL можно открыть эту схему, посмотреть типы колонок и сразу проверить запросы на небольшом наборе данных. Такой цикл полезнее, чем запоминать синтаксис отдельно от смысла таблиц.
| Колонка | Тип | Что означает |
|---|---|---|
| user_id | integer | уникальный идентификатор пользователя |
| signup_date | date | дата регистрации |
| channel | text | канал привлечения: organic, paid или referral |
SELECT: что показать в результате
SELECT задаёт форму результата. Если нужно проверить пользователей и их каналы, перечисли только эти поля. Явный список колонок помогает читать запрос и не тащит в ответ случайные технические поля.
select
user_id,
signup_date,
channel
from users;- Не используй
select *в итоговом отчёте: результат становится шире, а смысл — менее очевидным. - Порядок колонок в
SELECT— это порядок колонок в ответе. - Вместо поля можно вернуть выражение или вычисляемое значение, но сначала закрепи базовую выборку.
FROM: откуда читать строки
FROM users говорит базе, что источником результата является таблица users. Если у таблицы есть короткий псевдоним, запрос становится удобнее расширять: from users as u, а колонку можно написать как u.user_id.
Название таблицы само по себе ещё ничего не гарантирует. Перед расчётом проверь, какая строка в ней хранится, за какой период загружены данные и нет ли отдельного представления для аналитики. Ошибка на этом шаге не исправляется красивым синтаксисом.
Спроси себя: если я возьму одну строку из users, она описывает пользователя, регистрацию, сессию или событие? Ответ определит, можно ли считать строки напрямую или понадобится distinct и агрегация.
WHERE: как оставить нужные строки
WHERE превращает общий список в ответ на конкретный вопрос. Например, так можно оставить только пользователей из organic, зарегистрированных начиная с 1 июня. Строковые значения пишутся в одинарных кавычках, а дату лучше сравнивать с датой, а не с произвольным текстом.
select
user_id,
signup_date,
channel
from users
where channel = 'organic'
and signup_date >= date '2026-06-01'
and signup_date < date '2026-07-01';=проверяет точное совпадение, напримерchannel = 'organic'.andтребует, чтобы совпали оба условия.orоставляет строку, если подходит хотя бы одно условие; группируй такие условия скобками.- Для диапазона дат обычно удобно использовать левую включённую и правую исключённую границу.
Собери запрос по шагам
Теперь объединим базовые части. Задача: посмотреть пять первых organic-пользователей, зарегистрированных в июне, в порядке даты регистрации. ORDER BY делает порядок явным, а LIMIT сокращает результат для быстрой проверки.
Читать его удобно сверху вниз как последовательность решений: какие поля вернуть → из какой таблицы → какие строки оставить → в каком порядке показать → сколько строк вывести. Логика выполнения внутри базы сложнее, но для аналитика такой порядок чтения хорошо сохраняет смысл запроса.
select
user_id,
signup_date,
channel
from users
where channel = 'organic'
and signup_date >= date '2026-06-01'
and signup_date < date '2026-07-01'
order by signup_date, user_id
limit 5;Частые ошибки в первых запросах
На старте проблема обычно не в том, что запрос не запускается. Гораздо опаснее запрос, который запускается и даёт ответ не на тот вопрос.
Назови единицу строки, период, фильтры и ожидаемый размер ответа. Если не можешь объяснить эти четыре пункта словами, SQL ещё рано отправлять в дашборд.
- Считать строки
eventsкак пользователей: одно и то же действие может повторяться много раз. - Писать
select *и не замечать, что в отчёт попали технические поля или персональные данные. - Забывать кавычки вокруг текста:
organic— значение, а не название колонки. - Использовать
limitбезorder by, а потом считать полученную пятёрку “первыми” пользователями. - Сравнивать дату регистрации со строкой в непонятном формате и надеяться на неявное приведение типов.
- Не проверять
null: условиеchannel = 'organic'не включает пользователей с пустым каналом.
Что проверить перед первым запуском
Проверяй базовый запрос как маленькое исследование. Сначала выполни select count(*) from users, затем посмотри диапазон signup_date и распределение channel. Это поможет понять, что фильтр не возвращает пустой результат из-за опечатки, другой кодировки или периода вне учебного набора.
Для выборки добавь order by, иначе база не обязана возвращать строки в одинаковом порядке. limit полезен для осмотра, но не должен использоваться как часть метрики. Если нужно узнать число пользователей, убери LIMIT и добавь явную агрегацию.
Сравни несколько строк вручную с условием. Найди одного organic-пользователя, зарегистрированного в июне, и одного, который должен быть исключён. Такой контроль связывает SQL с данными, а не только с зелёным статусом выполнения.
| Проверка | Что ловит |
|---|---|
| count(*) | пустой или неожиданно большой результат |
| min/max date | не тот период или формат даты |
| distinct channel | опечатку в значении фильтра |
| order by + limit | стабильный просмотр примера |
SELECT отвечает на вопрос, но не объясняет причину
Первая выборка — это наблюдение, а не вывод. Если ты нашёл 120 organic-пользователей, следующий вопрос может быть про их активность, retention или конверсию в оплату. Не добавляй JOIN и расчёты сразу: сначала убедись, что базовая аудитория определена правильно.
Сохраняй запросы как маленькие шаги. Рабочий путь обычно выглядит так: посмотреть строки, проверить фильтр, посчитать группу, присоединить факт и сравнить сегменты. Такой порядок легче отлаживать и проще объяснить человеку, который только начинает SQL.
Когда результат станет частью отчёта, добавь период, размер базы и ссылку на определение события. Тогда даже простой SELECT будет полезен команде, а не останется одноразовым упражнением.
Мини-практика
Попробуй изменить последний запрос: оставь только пользователей из paid, выведи десять строк и отсортируй их по user_id. Затем замени диапазон дат на первые три дня июня. После этого можно переходить к агрегатам и считать, сколько пользователей пришло из каждого канала.
NULL: отсутствие значения — это не пустая строка
NULL означает, что значения нет или оно неизвестно. Это не то же самое, что пустая строка '', ноль или слово unknown. Сравнение channel = NULL не вернёт пользователей: для проверки отсутствия значения нужен IS NULL, а для наличия — IS NOT NULL.
В аналитике NULL часто имеет смысл. У пользователя ещё не определён канал, событие пришло без user_id, платёж не имеет даты возврата. Не удаляй такие строки автоматически. Сначала реши, должны ли они входить в метрику, а затем явно назови правило в описании расчёта.
| Значение | Что означает | Проверка |
|---|---|---|
| NULL | значение отсутствует или неизвестно | is null / is not null |
| '' | строка есть, но она пустая | channel = '' |
| 0 | числовое значение равно нулю | amount = 0 |
| unknown | явно записанная категория | channel = 'unknown' |
Даты: границы периода должны быть явными
Для месяца безопаснее использовать включённую нижнюю границу и исключённую верхнюю: signup_date >= date '2026-06-01' and signup_date < date '2026-07-01'. Так в запрос не нужно подставлять последний день месяца, а время 2026-06-30 23:59:59 не потеряется из-за округления.
Если поле хранит timestamp, уточни таймзону. “День” в UTC и день в часовом поясе продукта могут начинаться в разные моменты. Для DAU это способно менять число активных пользователей между датами. Правило преобразования должно быть одинаковым в отчёте, SQL и визуализации.
select
user_id,
signup_date
from users
where signup_date >= timestamp '2026-06-01 00:00:00'
and signup_date < timestamp '2026-07-01 00:00:00';Перед публикацией напиши: “полные календарные дни в таймзоне продукта” или другое точное правило. Одного названия поля недостаточно.
AND, OR и скобки меняют аудиторию
Условие с AND оставляет строки, которые подходят под оба правила. OR оставляет строку, если совпало хотя бы одно. В выражении channel = 'organic' or channel = 'referral' and signup_date >= ... база сначала применит приоритет операторов, и результат может отличаться от того, что имел в виду аналитик.
Группируй альтернативы скобками: (channel = 'organic' or channel = 'referral') and signup_date >= .... Скобки полезны не только для базы, но и для человека, который будет поддерживать запрос через месяц.
- Сначала выпиши каждое условие отдельной строкой.
- Скобками объедини альтернативные значения одного поля.
- Отдельно проверь результат только с датой и только с каналом.
- Сравни количество строк до и после добавления каждого фильтра.
Как отлаживать запрос, который возвращает не то
Не переписывай весь SQL, если результат странный. Убери фильтры и проверь базовый SELECT. Затем добавляй условия по одному, каждый раз фиксируя размер выборки. Если строки исчезли после конкретного условия, проблема находится в значении, диапазоне, NULL или типе данных.
Для сложного вопроса сохраняй промежуточные версии: аудитория без фильтров, аудитория после периода, аудитория после канала и финальный результат. Это простая ручная форма CTE: каждый шаг отвечает на один вопрос и позволяет объяснить расхождение.
| Шаг | Контрольный вопрос |
|---|---|
| 1. FROM | таблица существует и содержит ожидаемый период? |
| 2. SELECT | колонки имеют нужный тип и смысл? |
| 3. первый WHERE | фильтр возвращает хотя бы известный пример? |
| 4. следующий WHERE | сколько строк исчезло и почему? |
| 5. ORDER BY/LIMIT | просмотр стабилен и не влияет на метрику? |
База читает запрос не в том порядке, в каком вы его пишете
Запрос пишется как SELECT ... FROM ... WHERE ..., но логически всё происходит иначе: сначала база берёт строки из источника, затем отбрасывает не подходящие под условие, и только потом формирует колонки результата. Сортировка и ограничение выдачи применяются в самом конце, к уже готовому набору.
Из этого следует практическое правило, о которое спотыкаются почти все в первую неделю: псевдоним, придуманный в SELECT, обычно нельзя использовать в WHERE того же запроса. На момент фильтрации его ещё не существует. Если выражение нужно и для отбора, и для вывода, его либо повторяют, либо выносят на отдельный слой запроса.
Ещё одно следствие: LIMIT не делает запрос дешёвым. Он ограничивает то, что вы увидите, а не то, что база успела прочитать. На учебной базе это незаметно, на рабочей таблице событий — вполне ощутимо.
Из того же порядка растёт следующая тема курса. Когда появится группировка, окажется, что WHERE фильтрует отдельные строки до сборки групп, а условие по уже посчитанной сумме или количеству живёт в HAVING. Пока запрос не агрегирует, это различие не мешает — но помнить о нём стоит с первого дня.
Сквозной пример: три таблицы, на которых можно всё повторить
Дальше идёт разбор целиком — от вопроса коллеги до ответа, который можно защитить. Он построен на той же учебной базе «Маяка», что и SQL-тренажёр КейсПрактики: сервис для совместной работы, три таблицы, первая неделя июня. База маленькая нарочно — восемнадцать пользователей можно пересчитать руками и поймать себя на ошибке до того, как её увидит кто-то ещё.
Первое, что стоит сделать с незнакомой базой, — назвать зерно каждой таблицы одной фразой. Это занимает минуту и снимает половину будущих вопросов: становится видно, где строка означает человека, где действие, а где деньги.
| Таблица | Одна строка — это | Ключевые колонки |
|---|---|---|
| users | один зарегистрированный пользователь | user_id, signup_date, channel, country |
| events | одно действие пользователя в продукте | event_id, user_id, event_time, event_name |
| payments | один платёж | payment_id, user_id, paid_at, amount, plan |
Шаг 1. Уточнить вопрос раньше, чем открыть редактор
Менеджер продукта пишет в чат: «Пришли список пользователей из organic за начало июня». Звучит однозначно, но в этой фразе спрятаны как минимум три решения, и каждое меняет результат.
Что такое «начало июня» — первые три дня, первая неделя или весь месяц? Что такое «пользователи из organic» — те, у кого канал записан как organic в момент регистрации, или те, кто пришёл без рекламной метки вообще? И что именно нужно в ответе: список людей для выгрузки или их количество для слайда? От последнего зависит, будет ли в запросе SELECT полей или COUNT.
Уточнить это дешевле до запроса. Ответ «три пользователя» и ответ «шесть пользователей» одинаково верны для разных прочтений одной просьбы, но обсуждать вы их будете как ошибку.
| Прочтение | Условие периода | Сколько строк вернёт |
|---|---|---|
| первые три дня июня | signup_date >= '2026-06-01' AND signup_date < '2026-06-04' | 3 пользователя |
| первая неделя июня | signup_date >= '2026-06-01' AND signup_date < '2026-06-08' | 6 пользователей |
| весь июнь | signup_date >= '2026-06-01' AND signup_date < '2026-07-01' | 6 пользователей |
Шаг 2. Собрать запрос и посмотреть на размер выборки
Договорились: нужен список organic-пользователей за первые три дня июня, с датой регистрации и страной. Запрос собирается ровно из тех частей, которые разобраны выше, и читается как формулировка задачи.
Обратите внимание на две детали. Верхняя граница периода записана как «меньше 4 июня», а не «меньше или равно 3 июня» — так запрос не сломается, если однажды в колонке окажется время, а не только дата. И сортировка задана явно: без ORDER BY база не обязана возвращать строки в одном порядке, а список, который меняется между запусками, невозможно обсуждать.
SELECT
user_id,
signup_date,
country
FROM users
WHERE channel = 'organic'
AND signup_date >= DATE '2026-06-01'
AND signup_date < DATE '2026-06-04'
ORDER BY signup_date, user_id;Шаг 3. Сверить результат руками
Запрос вернул три строки. На учебной базе это можно проверить пересчётом: organic-пользователи регистрировались 1, 2, 3, 4, 5 и 6 июня — по одному в день, — значит в трёхдневное окно попадают ровно трое. Совпало.
Такая сверка кажется избыточной ровно до первого случая, когда она не сходится. На реальных данных пересчитать руками нельзя, но приём остаётся: посчитайте ожидаемый порядок величины заранее и сравните. «Ожидаю примерно сотню, получил четыре» — это сигнал искать опечатку в значении канала или неверную границу периода, а не радоваться быстрому ответу.
Полезно так же проверить дополнение выборки: сколько пользователей за тот же период пришло не из organic. Если сумма двух чисел не равна общему количеству регистраций за период, где-то потерялись строки — обычно из-за NULL в колонке канала. Запрос ниже считает все четыре числа сразу, и его удобно держать под рукой как шаблон проверки для любого фильтра по категории.
| user_id | signup_date | country |
|---|---|---|
| 1 | 2026-06-01 | RU |
| 5 | 2026-06-02 | BY |
| 8 | 2026-06-03 | RU |
SELECT
count(*) AS all_signups,
count(*) FILTER (WHERE channel = 'organic') AS organic_signups,
count(*) FILTER (WHERE channel <> 'organic') AS other_signups,
count(*) FILTER (WHERE channel IS NULL) AS unknown_channel
FROM users
WHERE signup_date >= DATE '2026-06-01'
AND signup_date < DATE '2026-06-04';Шаг 4. Отдать ответ так, чтобы его можно было перепроверить
Голое число или таблица без контекста порождают второй круг вопросов. К ответу стоит приложить три вещи: как определён период, что считается пользователем нужного канала и сколько всего строк было до фильтра. Это две строки текста, которые экономят получасовое обсуждение.
Формулировка может выглядеть так: «За 1–3 июня из organic зарегистрировались 3 пользователя из 9 регистраций за этот период. Канал берётся из users.channel на момент регистрации; период — по дате регистрации, верхняя граница не включена». Теперь любой коллега видит, какое именно решение вы приняли за него, и может возразить по существу.
Тот же принцип работает и дальше по курсу: чем сложнее запрос, тем важнее рядом с числом называть зерно, период и правило отбора. Метрику без этих трёх вещей нельзя ни проверить, ни повторить.
Минимальный чеклист перед первым SQL-ответом
Перед отправкой результата проверь не только зелёный статус выполнения. Назови зерно строки, период и таймзону, условия фильтра, отношение NULL и ожидаемый размер базы. Найди одну строку, которая должна попасть в ответ, и одну, которая должна быть исключена.
Если запрос отвечает на вопрос про пользователей, не называй его метрикой, пока не проверил уникальность user_id. Следующий шаг — агрегирование через GROUP BY и COUNT(DISTINCT), но базовая аудитория должна быть понятна заранее.
Что изучать дальше
После SELECT, FROM и WHERE логично перейти к GROUP BY, COUNT и COUNT(DISTINCT). Именно там список строк превращается в метрику: DAU по дням, пользователи по каналам или конверсия между шагами воронки. Но сначала закрепи базовую механику на нескольких собственных вопросах.
Материалы по теме

SQL для аналитика: как считать продуктовые метрики запросами
С чего начать в SQL, как читать grain, фильтровать события, считать уникальных пользователей, соединять таблицы и превращать результат в вывод по продукту.
Порядок выполнения SQL-запроса: почему WHERE не видит alias из SELECT
Разбираем логический порядок выполнения SQL-запроса: FROM, WHERE, GROUP BY, HAVING, SELECT и ORDER BY. Примеры помогают понять ошибки alias и агрегации.
WHERE и HAVING в SQL: разница на примерах аналитики
Понятное сравнение WHERE и HAVING в SQL: фильтрация строк до GROUP BY, фильтрация групп после агрегации и типичные ошибки аналитика.