SQL-запросы — инструкции языка структурированных запросов (Structured Query Language) для управления реляционными базами данных. Стандарт SQL описан в ISO/IEC 9075 и применяется во всех крупных системах управления базами данных (СУБД): PostgreSQL, MySQL, Oracle, SQL Server.
По данным hh.ru, SQL входит в обязательные требования более чем в 80% вакансий для аналитиков данных. При этом порог входа невысок: синтаксис строится почти как предложение на английском — SELECT name FROM users WHERE age > 25.
Учитесь бесплатно за счёт государства
Экономия до 100 000 ₽ на любой программе
В этой статье собраны sql запросы примеры всех уровней сложности: от минимального SELECT * до оконных функций с OVER (PARTITION BY). Разберём базовый синтаксис и порядок выполнения команд, затем GROUP BY и JOIN, подзапросы, обобщённые табличные выражения (CTE, Common Table Expressions) и оконные функции. В конце — типовые паттерны аналитика и частые ошибки начинающих.

Язык структурированных запросов создан в 1974 году в лаборатории IBM Research. В 1986 году принят стандартом ANSI, с 1987-го — стандартом ISO/IEC 9075. Сегодня он реализован в десятках СУБД: реляционных (PostgreSQL, MySQL, Oracle, SQL Server) и аналитических (ClickHouse, Redshift, BigQuery).
Реляционная база данных хранит информацию в связанных таблицах. Например, интернет-магазин может состоять из четырёх таблиц: customers (покупатели), orders (заказы), order_items (состав заказов) и products (товары). Таблицы связаны через первичные и внешние ключи (primary key / foreign key). Запрос к базе данных позволяет извлечь нужные данные из любой комбинации таких таблиц — одной командой.
Все команды языка структурированных запросов делятся на четыре подмножества: DDL управляет структурой базы данных, DML работает с данными, DCL регулирует права доступа, TCL управляет транзакциями. Управление базами данных через SQL позволяет решать задачи разной природы: от создания схемы до аналитических выгрузок.
SQL — способ задать базе данных структурированный вопрос и получить точный ответ. Аналитик не листает миллионы строк вручную: он пишет запрос и получает готовую выгрузку.
Три основных сценария применения:
Запросы к базе данных используются и в продуктовой аналитике, и в финансовом планировании, и в маркетинге. По данным hh.ru, знание SQL — один из топ-3 обязательных навыков для бизнес-аналитика наряду с Excel и визуализацией данных.
Полный шаблон SQL-запроса при написании выглядит так:
SELECT столбцы
FROM таблица
JOIN другая_таблица ON условие
WHERE условие_фильтрации
GROUP BY столбцы_группировки
HAVING условие_для_групп
ORDER BY столбец ASC|DESC
LIMIT N;
Обязательны только SELECT и FROM — остальные блоки подключаются по необходимости. Минимальный рабочий запрос выглядит так:
SELECT * FROM customers;
Ключевая точка путаницы для новичков: порядок написания не совпадает с порядком выполнения. СУБД обрабатывает запрос в следующей последовательности:
Этот порядок объясняет большинство типичных ошибок начинающих — например, почему агрегатную функцию нельзя написать в WHERE, а псевдоним из SELECT недоступен в WHERE и GROUP BY.
Все команды языка структурированных запросов делятся на четыре функциональных подмножества — в зависимости от того, с чем именно работает команда.
| Подмножество |
Расшифровка |
Основные команды |
Назначение |
|---|---|---|---|
| DDL | Data Definition Language | CREATE, ALTER, DROP, TRUNCATE | Управление структурой базы данных |
| DML | Data Manipulation Language | SELECT, INSERT, UPDATE, DELETE | Работа с данными в таблицах |
| DCL | Data Control Language | GRANT, REVOKE | Управление правами доступа |
| TCL | Transaction Control Language | COMMIT, ROLLBACK, SAVEPOINT | Управление транзакциями |
Помимо деления на подмножества, запросы разделяют по сложности: простые (одна таблица, без агрегаций), средние (GROUP BY + JOIN) и сложные (CTE, подзапросы, оконные функции). Простые запросы выполняются быстрее; сложные охватывают несколько таблиц и вычислительных шагов.
DDL (Data Definition Language) — подмножество SQL для определения структуры базы данных. Основные команды:
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name VARCHAR(100),
city VARCHAR(50),
created_at DATE
);
Важная особенность DDL: изменения фиксируются автоматически (auto-commit). Откатить CREATE TABLE или DROP TABLE через ROLLBACK невозможно — в этом принципиальное отличие DDL от DML.
DML (Data Manipulation Language) работает с содержимым таблиц, не затрагивая их структуру. Четыре основные команды:
INSERT INTO customers (name, city) VALUES (‘Анна’, ‘Москва’);
UPDATE customers SET city = ‘Санкт-Петербург’ WHERE customer_id = 1;
DELETE FROM customers WHERE customer_id = 1;
Изменения DML можно обернуть в транзакцию: если что-то пошло не так, команда ROLLBACK вернёт базу в исходное состояние. Структуру таблиц DML не изменяет — это зона ответственности DDL.
Простые sql запросы примеры строятся на трёх блоках: SELECT, FROM и необязательных WHERE и ORDER BY. Они работают с одной таблицей и не используют агрегаций или объединений — именно с них начинают изучение выборки данных.
SELECT определяет, какие столбцы попадут в результат. Символ * означает «все столбцы»; перечисление через запятую ограничивает выдачу нужными полями. Через AS задаётся псевдоним — удобно для вычисляемых полей и читаемости результата:
— Все столбцы таблицы
SELECT * FROM products;
— Конкретные столбцы + вычисляемое поле
SELECT
product_name,
price,
quantity,
price * quantity AS total_value
FROM order_items;
Псевдонимы улучшают читаемость и позволяют ссылаться на вычисляемый столбец в ORDER BY.
WHERE фильтрует строки до GROUP BY — это второй шаг в порядке выполнения запроса. Поддерживаемые операторы сравнения и поиска:
— Простая фильтрация данных
SELECT * FROM customers WHERE city = ‘Москва’;
— Список значений
SELECT * FROM products WHERE category IN (‘Электроника’, ‘Аксессуары’);
— Поиск по шаблону
SELECT * FROM customers WHERE name LIKE ‘Ан%’;
Условия комбинируются через AND, OR, NOT. Важно помнить: агрегатные функции (COUNT, SUM) в WHERE не работают — для фильтрации групп есть HAVING.
ORDER BY сортирует финальный результат запроса. По умолчанию применяется ASC — возрастание: числа от меньшего к большему, строки по алфавиту. DESC задаёт сортировку по убыванию.
— По убыванию цены
SELECT product_name, price
FROM products
ORDER BY price DESC;
— По двум столбцам: страна по алфавиту, внутри — город в обратном порядке
SELECT customer_id, country, city
FROM customers
ORDER BY country ASC, city DESC;
Сочетание ORDER BY DESC + LIMIT образует паттерн TOP-N — пять самых дорогих товаров, три последних заказа. В PostgreSQL дополнительно поддерживаются NULLS FIRST и NULLS LAST для управления позицией пустых значений в отсортированном списке.
Запросы среднего уровня агрегируют данные и объединяют несколько таблиц. Здесь открываются возможности для построения отчётов: продажи по городам, средний чек по категориям, история заказов клиента с детализацией позиций.
Логика прогрессии: простые запросы (одна таблица, фильтрация) → средние (GROUP BY + агрегатные функции, JOIN между таблицами) → сложные (подзапросы, CTE, оконные функции). Каждый уровень надстраивается над предыдущим, добавляя новые инструменты группировки данных и объединения таблиц.
GROUP BY группирует строки с одинаковыми значениями указанного столбца. Внутри каждой группы агрегатные функции вычисляют одно итоговое значение:
Правило SELECT: в список столбцов можно включать только те столбцы, которые указаны в GROUP BY, или агрегатные функции — ничего другого.
SELECT
city,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM orders
GROUP BY city
ORDER BY revenue DESC;
Этот запрос вернёт список городов с количеством заказов и суммарной выручкой, отсортированный по убыванию. Именно через такие sql запросы примеры строятся базовые аналитические отчёты.
HAVING — «WHERE для групп». Он фильтрует уже сгруппированные данные после GROUP BY, и именно здесь допустимы агрегатные функции.
| Параметр |
WHERE |
HAVING |
|---|---|---|
| Когда фильтрует | До GROUP BY | После GROUP BY |
| Что фильтрует | Отдельные строки | Группы строк |
| Агрегатные функции | ❌ нельзя | ✅ можно |
| Аналогия | Фильтр сырых данных | Фильтр итогов |
Типичная ошибка: WHERE COUNT(*) > 5 — SQL вернёт ошибку, потому что WHERE выполняется до GROUP BY и групп ещё не существует. Правильный вариант:
SELECT city, COUNT(*) AS cnt
FROM orders
WHERE order_date >= ‘2024-01-01’
GROUP BY city
Хотите сменить профессию или повысить квалификацию?
Федеральный проект «Активные меры содействия занятости» даёт возможность пройти обучение бесплатно за счёт государства
HAVING COUNT(*) > 5
ORDER BY cnt DESC;
В одном запросе могут присутствовать и WHERE, и HAVING: первый фильтрует строки до группировки, второй — группы после.
JOIN объединяет таблицы по ключу, заданному в условии ON. Блок JOIN идёт после FROM, до WHERE.
| Тип JOIN |
Описание |
Применение |
|---|---|---|
| INNER JOIN | Только строки с совпадениями в обеих таблицах | Заказы с известным клиентом |
| LEFT JOIN | Все строки левой таблицы + совпадения из правой (NULL при отсутствии) | Клиенты, в том числе без заказов |
| RIGHT JOIN | Все строки правой таблицы + совпадения из левой | Аналог LEFT JOIN с обратным порядком |
| FULL OUTER JOIN | Все строки из обеих таблиц независимо от совпадений | Полный реестр без потерь |
| CROSS JOIN | Декартово произведение: каждая строка × каждую | Генерация комбинаций |
Паттерн «поиск сирот» — LEFT JOIN с условием WHERE правая_таблица.id IS NULL — находит строки левой таблицы без пары в правой:
— Товары без единого заказа
SELECT p.product_name
FROM products p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
WHERE oi.product_id IS NULL;
Множественный JOIN последовательно объединяет более двух таблиц:
SELECT o.order_id, c.name, p.product_name, oi.quantity
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id;
Продвинутый уровень sql запросов примеры строится на трёх конструкциях: подзапросы, обобщённые табличные выражения (CTE) и оконные функции. Каждая решает свою задачу: подзапросы — вложенная логика фильтрации, CTE — читаемость и повторное использование, оконные функции — аналитика без сворачивания строк в группы.
Вложенный запрос — SELECT внутри другого запроса. Размещается в трёх позициях:
— Товары с ценой выше средней по каталогу (подзапрос в WHERE)
SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
Коррелированный подзапрос ссылается на столбцы внешнего запроса и выполняется для каждой строки — при больших таблицах это работает медленно. Его рекомендуют заменять на LEFT JOIN с GROUP BY: тот же результат, но СУБД обходит таблицу один раз вместо N раз.
Обобщённые табличные выражения определяются через ключевое слово WITH и существуют только в рамках одного SQL-выражения:
WITH customer_segments AS (
SELECT
customer_id,
SUM(total_amount) AS total_spent,
CASE
WHEN SUM(total_amount) > 100000 THEN ‘VIP’
WHEN SUM(total_amount) > 30000 THEN ‘Gold’
ELSE ‘Regular’
END AS segment
FROM orders
GROUP BY customer_id
)
SELECT segment, COUNT(*) AS customers
FROM customer_segments
GROUP BY segment;
Несколько CTE перечисляются через запятую до финального SELECT. Преимущество перед вложенным запросом — читаемость: именованный блок вынесен вперёд, логика разбивается на шаги. Рекурсивный CTE (WITH RECURSIVE) обрабатывает иерархические структуры — например, дерево категорий товаров или цепочку подчинённости сотрудников.
Оконные функции вычисляют результат по набору строк, не сворачивая их — в этом ключевое отличие от GROUP BY. GROUP BY на выходе даёт одну строку на группу; оконная функция добавляет вычисленное значение рядом с каждой строкой исходной таблицы.
Синтаксис: функция() OVER (PARTITION BY … ORDER BY …)
| Функция |
Описание |
Применение |
|---|---|---|
| ROW_NUMBER() | Уникальный порядковый номер строки внутри раздела | Нумерация заказов клиента |
| RANK() | Ранг с пропусками при совпадении значений | Рейтинг по продажам |
| DENSE_RANK() | Ранг без пропусков при совпадении | Плотный рейтинг |
| LAG() | Значение предыдущей строки по заданному порядку | Сравнение с прошлым периодом |
| LEAD() | Значение следующей строки | Прогноз следующего периода |
| SUM() OVER | Накопительная (кумулятивная) сумма | Нарастающий итог выручки |
Три практических примера:
— ROW_NUMBER: нумерация заказов внутри каждого клиента
SELECT
order_id,
customer_id,
order_date,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) AS order_num
FROM orders;
— LAG: сравнение текущей суммы с предыдущим месяцем
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_revenue
FROM monthly_revenue;
— SUM OVER: накопительная сумма
SELECT
order_date,
total_amount,
SUM(total_amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
Ключевая формулировка: «Как GROUP BY, но каждая строка остаётся в результате». Оконные функции незаменимы для рейтингов, динамики периодов и накопительных показателей.

Три паттерна, которые решают большинство аналитических задач:
Паттерн 1 — Топ-N по группе. ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) нумерует строки внутри каждой категории. Внешний запрос фильтрует WHERE rn <= 3 — и возвращает три самых дорогих товара в каждой категории без дополнительных объединений.
Паттерн 2 — Динамика период к периоду. DATE_TRUNC(‘month’, order_date) группирует данные по месяцам, LAG() достаёт выручку предыдущего месяца — получается сравнение месяц к месяцу без дополнительных таблиц и самообъединений.
Паттерн 3 — Поиск «сирот». LEFT JOIN + WHERE правая_таблица.id IS NULL — классический способ найти строки без пары: товары без ни одного заказа, клиенты без транзакций, категории без товаров.
Все три паттерна складываются из блоков, разобранных выше. Понимание того, как работают sql запросы примеры каждого уровня, позволяет комбинировать их под любую задачу — от простой выгрузки до многоуровневого аналитического отчёта.
Большинство ошибок новичков вытекают из непонимания порядка выполнения запроса — в частности, из того, что SELECT выполняется позже FROM и WHERE.
| Ошибка |
Неправильно |
Правильно |
|---|---|---|
| Агрегатная функция в WHERE | WHERE COUNT(*) > 5 | HAVING COUNT(*) > 5 |
| Сравнение с NULL через = | WHERE city = NULL | WHERE city IS NULL |
| Деление на ноль | revenue / users | revenue / NULLIF(users, 0) |
| GROUP BY без агрегатной функции в SELECT | SELECT city, name … GROUP BY city | SELECT city, COUNT(*) … GROUP BY city |
| HAVING вместо WHERE для неагрегированного условия | GROUP BY city HAVING city = ‘Москва’ | WHERE city = ‘Москва’ GROUP BY city |
Разберём три ключевых случая. Агрегатная функция в WHERE — результат непонимания того, что WHERE выполняется до GROUP BY: в этот момент групп ещё нет. Сравнение city = NULL всегда возвращает UNKNOWN, а не TRUE — для проверки на пустое значение SQL использует специальный оператор IS NULL / IS NOT NULL. Деление на ноль возникает в расчётах конверсий и средних: NULLIF(users, 0) вернёт NULL при нулевом знаменателе, и деление безопасно завершится NULL вместо ошибки. COALESCE(expression, 0) дополнительно заменяет NULL на ноль там, где нужен чистый числовой результат.
SQL — фундамент работы с данными, и освоить его реально за 2–3 месяца. В рамках федерального проекта «Активные меры содействия занятости» нацпроекта «Кадры» можно пройти обучение по программе «Специалист по аналитике и базам данных в информационных системах» с нуля, без отрыва от работы и за счёт государства. Смотрите каталог доступных программ — там же условия участия и запись.
WHERE фильтрует строки до GROUP BY — агрегатные функции (COUNT, SUM) в нём использовать нельзя. HAVING фильтрует уже сгруппированные данные после GROUP BY — именно здесь применяются условия COUNT(*) > 5 и аналогичные. Типичная ошибка: написать WHERE COUNT(*) > 5 — SQL вернёт ошибку. Правильно: GROUP BY city HAVING COUNT(*) > 5.
Подзапрос — SELECT внутри другого запроса. Используется в WHERE (отбор по условию: цена выше средней), FROM (временная таблица) и SELECT (вычисляемое поле для каждой строки). Коррелированный подзапрос выполняется для каждой строки внешнего запроса — при больших таблицах медленнее. Его часто заменяют на JOIN с GROUP BY для повышения производительности.
CTE (WITH … AS) именован и вынесен до основного запроса — читается как пошаговый алгоритм. Подзапрос встроен в тело и затрудняет чтение при нескольких уровнях вложенности. CTE можно переиспользовать несколько раз в одном выражении. Рекурсивный CTE (WITH RECURSIVE) обрабатывает иерархические данные — то, что подзапрос сделать не может.
GROUP BY сворачивает строки: на выходе одна строка на группу. Оконные функции оставляют все исходные строки и добавляют рядом агрегированный результат. ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) пронумерует заказы каждого клиента — GROUP BY на это не способен. Оконные функции незаменимы для рейтингов, накопительных сумм и сравнения с предыдущим периодом.
INNER JOIN — только совпадающие строки из обеих таблиц. LEFT JOIN — все строки левой таблицы плюс совпадения из правой (NULL если нет пары). RIGHT JOIN — зеркало LEFT JOIN. FULL OUTER JOIN — все строки из обеих таблиц независимо от совпадений. CROSS JOIN — декартово произведение всех комбинаций. В аналитике чаще всего используются INNER JOIN и LEFT JOIN.
Паттерн «поиск сирот»: LEFT JOIN + WHERE правая_таблица.id IS NULL. Пример — товары без единого заказа: SELECT p.product_name FROM products p LEFT JOIN order_items oi ON p.product_id = oi.product_id WHERE oi.product_id IS NULL. LEFT JOIN возвращает все товары; там, где заказов нет, order_items.product_id равен NULL — это и есть условие фильтрации.
DDL (CREATE, ALTER, DROP) управляет структурой таблиц: создаёт, изменяет, удаляет схему. Изменения DDL фиксируются автоматически (auto-commit) — откатить через ROLLBACK нельзя. DML (SELECT, INSERT, UPDATE, DELETE) работает с данными в уже существующих таблицах и не меняет их структуру. Изменения DML можно обернуть в транзакцию и при необходимости откатить.
GROUP BY country, city группирует строки по каждой уникальной комбинации значений — отдельно «Германия, Берлин», «Германия, Мюнхен» и т.д. В SELECT обязательно перечислить те же столбцы плюс агрегатные функции: SELECT country, city, COUNT(*) FROM customers GROUP BY country, city. Порядок столбцов в GROUP BY влияет только на читаемость, не на результат.
ORDER BY сортирует финальный результат запроса. ASC — возрастание (по умолчанию): числа от меньшего, строки по алфавиту. DESC — убывание. Можно комбинировать: ORDER BY category ASC, price DESC — сортировка сначала по категории, внутри категории по цене от дорогого. Вместе с LIMIT реализует паттерн TOP-N: ORDER BY price DESC LIMIT 5 — пять самых дорогих товаров.
Начните с SELECT + WHERE + ORDER BY — они покрывают 80% задач начинающего аналитика. Затем добавьте GROUP BY с агрегатными функциями и JOIN для работы с несколькими таблицами. После этого переходите к подзапросам и CTE, финально — к оконным функциям. Программа «Специалист по аналитике и базам данных в информационных системах» охватывает весь этот путь с практикой на реальных наборах данных.
Подайте заявку —
забронируйте место в группе
45 000 мест на 2026 год. Бесплатное обучение по федеральному проекту «Активные меры содействия занятости»