Все материалы
Продуктовая аналитикагайдстарт

SQL с нуля: как работают SELECT, FROM и WHERE

Разбираем первый SQL-запрос на понятном примере: как выбрать колонки, указать таблицу, отфильтровать строки и не перепутать результат с данными продукта.

КПКейсПрактика13 июля 2026 г.15 мин
Содержание статьи

Первый SQL-запрос лучше учить не на абстрактных цифрах, а на вопросе из реальной работы: “Покажи пользователей, которые пришли из organic в июне”. В нём уже есть всё необходимое — какие поля нужны, из какой таблицы их взять и какие строки оставить. Разберём эту логику на простом примере и закончим запросом, который можно выполнить в бесплатном тренажёре КейсПрактики.

Коротко

У базового запроса есть три главные детали: SELECT выбирает столбцы в результате, FROM указывает источник данных, а WHERE отбрасывает строки, которые не подходят под условие. Порядок написания всегда такой: сначала что показать, затем откуда взять, затем что оставить.

  • SELECT — какие поля или расчёты вернуть.
  • FROM — из какой таблицы или представления читать данные.
  • WHERE — какие строки подходят под вопрос.
  • Перед запросом нужно понять, что означает одна строка в таблице.
  • Для предсказуемой выборки добавляй ORDER BY, а для короткого примера — LIMIT.

Сначала разберись со структурой данных

Представим учебную таблицу users. В ней одна строка — один пользователь. Это важно зафиксировать до запроса: если одна строка уже описывает пользователя, можно читать список пользователей напрямую. В таблице events одна строка, наоборот, обычно означает одно событие, поэтому подсчёт строк там не равен подсчёту людей.

В тренажёре SQL можно открыть эту схему, посмотреть типы колонок и сразу проверить запросы на небольшом наборе данных. Такой цикл полезнее, чем запоминать синтаксис отдельно от смысла таблиц.

Учебная таблица users
КолонкаТипЧто означает
user_idintegerуникальный идентификатор пользователя
signup_datedateдата регистрации
channeltextканал привлечения: 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 июня. Строковые значения пишутся в одинарных кавычках, а дату лучше сравнивать с датой, а не с произвольным текстом.

Пользователи из organic, зарегистрированные в июне
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 с данными, а не только с зелёным статусом выполнения.

Минимальный smoke-test для SELECT
ПроверкаЧто ловит
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: каждый шаг отвечает на один вопрос и позволяет объяснить расхождение.

Пошаговая отладка SELECT
ШагКонтрольный вопрос
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 база не обязана возвращать строки в одном порядке, а список, который меняется между запусками, невозможно обсуждать.

Список organic-пользователей за первые три дня июня
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_idsignup_datecountry
12026-06-01RU
52026-06-02BY
82026-06-03RU
Контрольный запрос: сходится ли выборка с общим числом регистраций
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 по дням, пользователи по каналам или конверсия между шагами воронки. Но сначала закрепи базовую механику на нескольких собственных вопросах.

Продолжить чтение
Вся библиотека