SQL для начинающих: как работает и основные запросы с примерами

Аналитика 12 мин чтения

SQL (Structured Query Language) — это язык запросов к реляционным базам данных: на нём создают таблицы, добавляют и меняют строки, выбирают и считают данные. Запрос описывает, какой результат нужен, а СУБД сама решает, как его получить. Начинающему хватит шести операторов: SELECT, FROM, WHERE, GROUP BY, ORDER BY и JOIN.

1986SQL стал стандартом ANSI, в 1987 году — стандартом ISO
SQL:2023действующая версия стандарта ISO/IEC 9075
5групп команд: DDL, DML, DQL, DCL, TCL
7шагов логического порядка выполнения SELECT

Главное за 30 секунд

  • SQL — декларативный язык: вы пишете, что получить, СУБД выбирает способ.
  • Порядок выполнения — FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT. Поэтому псевдоним из SELECT нельзя использовать в WHERE.
  • WHERE и HAVING — WHERE фильтрует строки до группировки, HAVING фильтрует группы после неё.
  • INNER JOIN оставляет строки с парой в обеих таблицах, LEFT JOIN сохраняет все строки левой таблицы.
  • NULL — отсутствие значения. COUNT(колонка) пропускает NULL, COUNT(*) считает все строки.

Что такое SQL простыми словами?

SQL — язык, на котором человек или программа обращается к базе данных. База хранит данные в таблицах: строка — одна запись, колонка — одно свойство записи.

Запросы выполняет СУБД: PostgreSQL, MySQL, SQLite, Microsoft SQL Server, ClickHouse. Основа языка описана в стандарте ISO/IEC 9075, но у каждой СУБД свой диалект. Например, первые строки выдачи в PostgreSQL отбирают через LIMIT, в SQL Server — через TOP.

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

Какая структура у SQL-запроса?

Запрос на выборку состоит из блоков-операторов, которые пишут в строгом порядке. Обязателен только SELECT, остальные блоки добавляют по задаче.

SELECT
какие колонки и вычисления вывести
FROM / JOIN
из каких таблиц брать строки и как их связать
WHERE
условие отбора строк
GROUP BY
по каким колонкам собрать строки в группы
HAVING
условие отбора групп
ORDER BY
сортировка результата
LIMIT
сколько строк вернуть

Как работает SQL-запрос?

СУБД выполняет блоки запроса в другом порядке, чем их пишут: сначала берёт таблицу, потом фильтрует и группирует строки, и только после этого вычисляет SELECT. Физически СУБД может переставить операции, если результат не изменится.

  1. FROM и JOIN. СУБД берёт и соединяет таблицы.
  2. WHERE. Отбрасывает строки по условию.
  3. GROUP BY. Собирает строки в группы и считает агрегаты.
  4. HAVING. Отбрасывает группы по условию на агрегат.
  5. SELECT. Вычисляет колонки и псевдонимы AS.
  6. ORDER BY. Сортирует строки, псевдонимы из SELECT уже доступны.
  7. LIMIT. Обрезает выдачу.

Разберём запрос со всеми блоками: два клиента с самой большой оплаченной выручкой, у которых она выше 15 000 ₽.

SELECT   client_id, SUM(amount) AS revenueFROM     ordersWHERE    status = 'paid'GROUP BY client_idHAVING   SUM(amount) > 15000ORDER BY revenue DESCLIMIT    2;
client_idrevenue
431200
223000

Псевдоним revenue работает в ORDER BY, потому что ORDER BY выполняется после SELECT. В HAVING мы повторили SUM(amount): по стандарту HAVING выполняется раньше SELECT. SQLite допускает псевдоним в HAVING. PostgreSQL вернёт ошибку: по его документации псевдонимы работают только в ORDER BY и GROUP BY.

Какие виды команд есть в SQL?

Команды SQL делят на пять групп по назначению. В документации Oracle SELECT относится к DML, а DQL выделяют в учебниках, чтобы отделить чтение данных от их изменения.

ГруппаРасшифровкаЧто делаетКоманды
DDLData Definition Languageсоздаёт и меняет структуру: таблицы, индексы, представленияCREATE, ALTER, DROP, TRUNCATE
DMLData Manipulation Languageдобавляет, меняет и удаляет строкиINSERT, UPDATE, DELETE
DQLData Query Languageчитает данныеSELECT
DCLData Control Languageвыдаёт и отзывает права доступаGRANT, REVOKE
TCLTransaction Control Languageуправляет транзакциямиBEGIN, COMMIT, ROLLBACK, SAVEPOINT

Аналитик пишет в основном DQL. В SQLite команд GRANT и REVOKE нет: доступ к файлу базы регулирует операционная система.

На какой базе разбираем примеры?

Все запросы ниже выполнены в SQLite 3.51.0 через модуль sqlite3 из стандартной библиотеки Python. Таблицы под запросами — реальный вывод. База — две таблицы интернет-магазина: клиенты и их заказы. Структуру задаёт DDL-команда CREATE TABLE:

CREATE TABLE clients (    id          INTEGER PRIMARY KEY,    name        TEXT NOT NULL,    city        TEXT,    signup_date TEXT);CREATE TABLE orders (    id         INTEGER PRIMARY KEY,    client_id  INTEGER REFERENCES clients(id),    order_date TEXT,    amount     INTEGER,    status     TEXT);

Таблица clients

SELECT * FROM clients;
idnamecitysignup_date
1Анна КотоваМосква2025-11-03
2Борис ЛебедевКазань2025-12-15
3Вера СоколоваМосква2026-01-20
4Глеб ОрловНовосибирск2026-02-02
5Дарья МироноваNULL2026-02-18
6Егор ВолковКазань2026-03-05
7Жанна БеловаМосква2026-03-27

У Дарьи Мироновой город не указан (NULL), у Жанны Беловой нет заказов.

Таблица orders

SELECT * FROM orders;
idclient_idorder_dateamountstatus
10112026-01-1012500paid
10212026-02-148300paid
10322026-01-2523000paid
10432026-02-034700cancelled
10532026-03-1115600paid
10642026-03-0231200paid
10752026-03-199900paid
10862026-04-056400new

Колонка client_id ссылается на clients.id, по ней таблицы соединяют.

Основные SQL-запросы с примерами

SELECT и WHERE: выбрать строки по условию

Оплаченные заказы от 10 000 ₽. Условия в WHERE соединяют через AND и OR.

SELECT id, client_id, amountFROM   ordersWHERE  status = 'paid' AND amount >= 10000;
idclient_idamount
101112500
103223000
105315600
106431200

ORDER BY и LIMIT: отсортировать и взять первые строки

Три самых крупных заказа. DESC сортирует по убыванию, без него сортировка идёт по возрастанию.

SELECT id, order_date, amountFROM     ordersORDER BY amount DESCLIMIT    3;
idorder_dateamount
1062026-03-0231200
1032026-01-2523000
1052026-03-1115600

Агрегатные функции: посчитать итоги

Итоги по оплаченным заказам:

SELECT COUNT(*)              AS orders_cnt,       SUM(amount)           AS revenue,       ROUND(AVG(amount), 0) AS avg_check,       MIN(amount)           AS min_order,       MAX(amount)           AS max_orderFROM   ordersWHERE  status = 'paid';
orders_cntrevenueavg_checkmin_ordermax_order
610050016750830031200

GROUP BY и HAVING: итоги по группам

GROUP BY считает агрегаты отдельно для каждой группы, HAVING фильтрует уже посчитанные группы. Клиенты, у которых два и больше оплаченных заказа:

SELECT   client_id, COUNT(*) AS cnt, SUM(amount) AS revenueFROM     ordersWHERE    status = 'paid'GROUP BY client_idHAVING   COUNT(*) >= 2;
client_idcntrevenue
1220800

Условие на COUNT(*) ставят только в HAVING: в момент WHERE групп ещё нет.

JOIN: соединить две таблицы

INNER JOIN (можно писать JOIN) оставляет строки, у которых нашлась пара в обеих таблицах. Имя клиента рядом с каждым заказом:

SELECT c.name, o.id AS order_id, o.amountFROM   clients AS cJOIN   orders  AS o ON o.client_id = c.idORDER BY o.id;
nameorder_idamount
Анна Котова10112500
Анна Котова1028300
Борис Лебедев10323000
Вера Соколова1044700
Вера Соколова10515600
Глеб Орлов10631200
Дарья Миронова1079900
Егор Волков1086400

Жанны Беловой в выдаче нет: у неё нет заказов. LEFT JOIN сохраняет все строки левой таблицы и подставляет NULL, где пары нет:

SELECT    c.name, COUNT(o.id) AS orders_cnt, COALESCE(SUM(o.amount), 0) AS totalFROM      clients AS cLEFT JOIN orders  AS o ON o.client_id = c.idGROUP BY  c.id, c.nameORDER BY  total DESC;
nameorders_cnttotal
Глеб Орлов131200
Борис Лебедев123000
Анна Котова220800
Вера Соколова220300
Дарья Миронова19900
Егор Волков16400
Жанна Белова00

COUNT(o.id) дал Жанне 0, потому что пропускает NULL. COALESCE заменил пустую сумму на 0. У Веры Соколовой в сумме есть отменённый заказ: фильтра по статусу нет.

Вид JOINЧто возвращаетСтрок на нашей базе
INNER JOINтолько пары, которые нашлись в обеих таблицах8
LEFT JOINвсе строки левой таблицы, справа NULL, если пары нет9
RIGHT JOINвсе строки правой таблицы, слева NULL, если пары нет8
FULL OUTER JOINвсе строки обеих таблиц9
CROSS JOINкаждую строку левой таблицы с каждой строкой правой56 (7 × 8)

Число строк посчитано в SQLite для каждого вида JOIN. RIGHT и FULL OUTER JOIN появились в SQLite в версии 3.39.0.

Решите 150 задач на SQL в тренажёре

В модуле SQL программы SF Education 27 уроков, 2 бизнес-кейса и 150 задач в тренажёре. Первые 48 часов доступа бесплатны.

Подзапрос: запрос внутри запроса

СУБД считает подзапрос в скобках и подставляет результат во внешний запрос. Заказы выше среднего чека (16 750 ₽):

SELECT id, client_id, amountFROM   ordersWHERE  status = 'paid'  AND  amount > (SELECT AVG(amount) FROM orders WHERE status = 'paid');
idclient_idamount
103223000
106431200

Подзапрос может вернуть и список: WHERE id IN (SELECT client_id FROM orders WHERE amount > 20000).

CTE: именованный подзапрос через WITH

CTE (Common Table Expression) задаёт временную таблицу в начале запроса, на неё ссылаются по имени. Выручка и доля городов:

WITH paid AS (    SELECT c.city, o.amount    FROM   orders  AS o    JOIN   clients AS c ON c.id = o.client_id    WHERE  o.status = 'paid')SELECT   COALESCE(city, 'не указан') AS city,         SUM(amount) AS revenue,         ROUND(100.0 * SUM(amount) / (SELECT SUM(amount) FROM paid), 1) AS share_pctFROM     paidGROUP BY cityORDER BY revenue DESC;
cityrevenueshare_pct
Москва3640036.2
Новосибирск3120031
Казань2300022.9
не указан99009.9

Таблицу paid мы использовали дважды: для группировки и для суммы в знаменателе. Множитель 100.0 нужен для дробного результата: SQLite и PostgreSQL при делении целых отбрасывают дробную часть. Нарастающие итоги, ранги и сравнение с прошлым месяцем считают оконными функциями. Их мы разобрали в отдельной статье про оконные функции SQL.

Какие функции SQL нужны начинающему?

Начинающему хватит пяти групп: агрегатные, строковые, даты, CASE и COALESCE. Сильнее всего между СУБД отличаются функции дат.

ГруппаФункцииЧто делаютПример
АгрегатныеCOUNT, SUM, AVG, MIN, MAXсчитают итог по группеSUM(amount)
СтроковыеUPPER, LOWER, LENGTH, SUBSTR, TRIM, REPLACEменяют регистр, режут текстSUBSTR(name, 1, 1)
ДатыSQLite: date, strftime; PostgreSQL: EXTRACT, date_truncдостают из даты месяц или годstrftime('%m', signup_date)
CASECASE WHEN … THEN … ELSE … ENDзначение по условию, как ЕСЛИ в ExcelCASE WHEN amount > 10000 THEN 'крупный' END
COALESCECOALESCE(a, b, …)первый аргумент, отличный от NULLCOALESCE(city, 'не указан')

Все пять групп в одном запросе:

SELECT name,       UPPER(SUBSTR(name, 1, 1))      AS initial,       COALESCE(city, 'не указан')    AS city,       strftime('%m', signup_date)    AS month,       CASE WHEN signup_date < '2026-01-01' THEN 'до 2026'            ELSE '2026' END           AS cohortFROM   clientsORDER BY idLIMIT  5;
nameinitialcitymonthcohort
Анна КотоваАМосква11до 2026
Борис ЛебедевБКазань12до 2026
Вера СоколоваВМосква012026
Глеб ОрловГНовосибирск022026
Дарья МироноваДне указан022026

Сравнение city = NULL не вернёт ни одной строки, NULL проверяют через IS NULL. Агрегаты по колонке пропускают NULL: в таблице clients 7 строк, а COUNT(city) возвращает 6.

Задачи по SQL для самопроверки

Напишите запрос сами, потом откройте ответ с решением и результатом из SQLite.

Задача 1. Выведите клиентов из Москвы в порядке регистрации.
SELECT name, signup_dateFROM   clientsWHERE  city = 'Москва'ORDER BY signup_date;
namesignup_date
Анна Котова2025-11-03
Вера Соколова2026-01-20
Жанна Белова2026-03-27
Задача 2. Посчитайте число оплаченных заказов и выручку по месяцам.
SELECT   strftime('%Y-%m', order_date) AS month,         COUNT(*)    AS orders_cnt,         SUM(amount) AS revenueFROM     ordersWHERE    status = 'paid'GROUP BY monthORDER BY month;
monthorders_cntrevenue
2026-01235500
2026-0218300
2026-03356700

strftime('%Y-%m', …) оставляет от даты год и месяц. В PostgreSQL для этого пишут date_trunc('month', order_date).

Задача 3. Найдите клиентов, у которых нет ни одного заказа.
SELECT    c.nameFROM      clients AS cLEFT JOIN orders  AS o ON o.client_id = c.idWHERE     o.id IS NULL;
name
Жанна Белова

LEFT JOIN подставляет NULL в колонки заказа, если пары нет. Условие o.id IS NULL оставляет только такие строки.

Задача 4. Выведите города со средним оплаченным чеком выше 10 000 ₽.
SELECT   COALESCE(c.city, 'не указан') AS city,         ROUND(AVG(o.amount), 0)       AS avg_checkFROM     orders  AS oJOIN     clients AS c ON c.id = o.client_idWHERE    o.status = 'paid'GROUP BY c.cityHAVING   AVG(o.amount) > 10000ORDER BY avg_check DESC;
cityavg_check
Новосибирск31200
Казань23000
Москва12133

Условие на AVG ставят в HAVING. Строка без города выпала: её средний чек 9 900 ₽.

Задача 5. Найдите клиента с самым крупным оплаченным заказом.
WITH top AS (    SELECT client_id, amount    FROM   orders    WHERE  status = 'paid'    ORDER BY amount DESC    LIMIT  1)SELECT c.name, top.amountFROM   topJOIN   clients AS c ON c.id = top.client_id;
nameamount
Глеб Орлов31200

Ту же задачу решает подзапрос: WHERE amount = (SELECT MAX(amount) FROM orders WHERE status = 'paid').

Как разбирать задачи на собеседовании, мы рассказали в статье о подготовке к интервью. Другие навыки, которые проверяют у кандидатов, — в материале о навыках бизнес-аналитика.

Частые вопросы

Чем SQL отличается от MySQL и PostgreSQL?

SQL — язык запросов. MySQL и PostgreSQL — СУБД, программы, которые хранят данные и выполняют запросы на SQL. Базовые SELECT, JOIN и GROUP BY работают в них одинаково, различаются функции дат, типы данных и отдельные операторы.

Где тренироваться писать SQL-запросы с нуля?

Подойдёт SQLite: база хранится в одном файле, сервер не нужен. Модуль sqlite3 входит в стандартную библиотеку Python. Скопируйте из статьи CREATE TABLE, добавьте строки через INSERT, и у вас будет та же база, что в примерах.

Чем WHERE отличается от HAVING?

WHERE отбирает строки до группировки и не видит агрегатов. HAVING отбирает группы после GROUP BY и работает с SUM, COUNT, AVG. Условие на обычную колонку ставят в WHERE.

Какие вопросы по SQL задают на собеседовании аналитика?

Проверьте, что можете объяснить порядок выполнения запроса, разницу WHERE и HAVING, виды JOIN, поведение NULL в агрегатах и разницу подзапроса и CTE. Отдельной темой идут оконные функции.

Пишите SQL-запросы для отчётов самостоятельно

Модуль SQL длится 1 месяц: 13 часов теории, 21 час практики, 150 задач и 2 бизнес-кейса. Выпускники получают удостоверение со сведениями в ФРДО.

Что почитать дальше
Александр Вальцев
Автор статьи Александр Вальцев Сооснователь и CEO SF Education, эксперт по корпоративным финансам
Корп. финансы Финмоделирование Инвестиции EdTech Аналитика
10+ лет в EdTech
100 000+ студентов
×150 рост за 5 лет
1000+ часов контента
  • Сооснователь и CEO SF Education — ТОП-10 по качеству образования (РБК), ТОП-100 перспективных стартапов (HolonIQ)
  • 2 года в корпоративных финансах: private equity в Канаде, investment banking в России
  • Автор курсов и вебинаров по финансовому моделированию, лектор в вузах, организатор кейс-чемпионатов в Казахстане и Узбекистане
Образование: University of Toronto (BCom in Finance), CFA Level 1