{}const=>[]async()letfn</>var
ПрактикаОсновыBackend

10 задач по SQL для начинающих: SELECT, WHERE, JOIN и GROUP BY

Одна учебная база, десять задач с ожидаемыми результатами и решениями: от простого фильтра до LEFT JOIN, агрегатов и HAVING.

К

Кодик

Автор

7 мин чтения

Чтобы научиться SQL, недостаточно прочитать синтаксис: нужно самому сформулировать запрос, запустить его и сравнить строки с ожидаемым результатом. Ниже одна учебная база и 10 задач с решениями: от SELECT и WHERE до JOIN, GROUP BY и HAVING. Все запросы совместимы с PostgreSQL и почти без изменений работают в SQLite.

Работайте в два прохода. Сначала читайте только условие и пишите свой запрос. Затем открывайте решение, сравнивайте не текст SQL, а полученные строки. Один результат можно получить разными корректными запросами.

1Фильтруем строки

SELECT, WHERE, AND, ORDER BY и LIMIT отвечают на точные вопросы.

2Считаем группы

COUNT, AVG и GROUP BY превращают много заказов в сводку.

3Соединяем таблицы

JOIN связывает заказ с пользователем по user_id и id.

Подготовка учебной базы users и orders

У нас интернет-магазин с двумя таблицами. В users лежат пользователи, в orders их заказы. Поле orders.user_id ссылается на users.id. Выполните скрипт один раз в пустой базе.

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    city VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id),
    total NUMERIC(10, 2) NOT NULL,
    status VARCHAR(20) NOT NULL,
    created_at DATE NOT NULL
);

INSERT INTO users (id, name, city) VALUES
(1, 'Анна', 'Москва'),
(2, 'Борис', 'Казань'),
(3, 'Вера', 'Москва'),
(4, 'Глеб', 'Тула'),
(5, 'Даша', 'Самара');

INSERT INTO orders (id, user_id, total, status, created_at) VALUES
(101, 1, 1200, 'paid', '2026-08-10'),
(102, 1, 800, 'new', '2026-08-12'),
(103, 2, 2500, 'paid', '2026-08-13'),
(104, 3, 700, 'cancelled', '2026-08-14'),
(105, 3, 1800, 'paid', '2026-08-15'),
(106, 5, 400, 'paid', '2026-08-16'),
(107, 2, 900, 'paid', '2026-08-17'),
(108, 3, 600, 'paid', '2026-08-18');
ТаблицаОдна строка означаетКлюч
usersОдного зарегистрированного человекаid
ordersОдин оформленный заказid
СвязьПользователь может иметь 0, 1 или много заказовorders.user_id = users.id
База готова. Запросы SELECT COUNT(*) FROM users; и SELECT COUNT(*) FROM orders; возвращают 5 и 8. Если числа другие, очистите учебные таблицы и выполните setup заново.
🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

Задачи 1-4: SELECT, WHERE, ORDER BY и LIMIT

1. Выведите имена и города всех пользователей
  1. Нужны только столбцы name и city.
  2. Количество ожидаемых строк: 5.
  3. Не используйте SELECT *: выбирайте данные, которые просит условие.
SELECT name, city
FROM users;
2. Найдите пользователей из Москвы
  1. Добавьте фильтр по полю city.
  2. Строковые значения заключаются в одинарные кавычки.
  3. Ожидаемые имена: Анна и Вера.
SELECT id, name
FROM users
WHERE city = 'Москва';
3. Найдите оплаченные заказы дороже 1000
  1. Нужны одновременно два условия: статус paid и сумма больше 1000.
  2. Отсортируйте результат от большей суммы к меньшей.
  3. Ожидаемый порядок id: 103, 105, 101.
SELECT id, user_id, total
FROM orders
WHERE status = 'paid' AND total > 1000
ORDER BY total DESC;
4. Покажите три самых дорогих заказа
  1. Статус заказа не важен.
  2. Сначала сортировка по убыванию, затем ограничение числа строк.
  3. Ожидаемые суммы: 2500, 1800 и 1200.
SELECT id, total, status
FROM orders
ORDER BY total DESC
LIMIT 3;
Что вы уже умеете

SELECT выбирает столбцы, WHERE отбрасывает строки, ORDER BY задаёт порядок, LIMIT оставляет нужное число строк. Порядок этих частей в запросе фиксирован.

Схема обработки SQL-запроса от FROM и WHERE до ORDER BY и LIMIT
Текст запроса читается сверху вниз, но для рассуждения полезно сначала представить исходные строки и фильтр.

Задачи 5-6: агрегаты и GROUP BY

Агрегатная функция сворачивает несколько строк в одно значение. Без GROUP BY весь результат считается одной группой. С GROUP BY status база создаёт отдельную группу для каждого статуса.

5. Посчитайте количество заказов каждого статуса
  1. В результате одна строка соответствует одному status.
  2. Дайте вычисленному столбцу имя orders_count через AS.
  3. Ожидается: paid = 6, new = 1, cancelled = 1.
SELECT status, COUNT(*) AS orders_count
FROM orders
GROUP BY status
ORDER BY orders_count DESC;
6. Найдите средний чек оплаченного заказа
  1. Сначала оставьте только paid-строки через WHERE.
  2. Затем примените AVG(total).
  3. Ожидаемое значение без округления: примерно 1233.33.
SELECT AVG(total) AS average_paid_total
FROM orders
WHERE status = 'paid';
WHERE работает до группировки

В шестой задаче отменённый и новый заказы не участвуют в среднем: WHERE убирает их до того, как AVG начинает вычисление.

Схема группировки заказов по статусу и применения COUNT
GROUP BY не сортирует результат: если порядок важен, добавляйте ORDER BY отдельно.

Синтаксис можно закрепить в курсе SQL: короткие упражнения помогают увидеть разницу между фильтром строк и групп без копирования готового ответа.

Задачи 7-9: INNER JOIN и LEFT JOIN

В orders хранится только user_id, но в отчёте нужен name. JOIN соединяет строку заказа со строкой пользователя по условию o.user_id = u.id. Алиасы o и u сокращают запрос и показывают источник каждого столбца.

7. Выведите заказ вместе с именем покупателя
  1. Используйте INNER JOIN: нужны только существующие заказы с найденным пользователем.
  2. Выведите id заказа, name, total и status.
  3. Количество ожидаемых строк: 8.
SELECT o.id, u.name, o.total, o.status
FROM orders AS o
JOIN users AS u ON u.id = o.user_id
ORDER BY o.id;
8. Посчитайте оплаченную выручку по покупателям
  1. Соедините users и orders, оставьте paid-заказы.
  2. Сгруппируйте строки по id и name пользователя.
  3. Ожидаемые суммы: Борис 3400, Вера 2400, Анна 1200, Даша 400.
SELECT u.id, u.name, SUM(o.total) AS paid_revenue
FROM users AS u
JOIN orders AS o ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY u.id, u.name
ORDER BY paid_revenue DESC;
9. Покажите число заказов у всех пользователей, включая ноль
  1. Начните с users и примените LEFT JOIN, чтобы Глеб не исчез.
  2. Считайте o.id, а не звёздочку.
  3. Ожидается пять строк; у Глеба orders_count = 0.
SELECT u.name, COUNT(o.id) AS orders_count
FROM users AS u
LEFT JOIN orders AS o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY u.id;

Сравнение INNER JOIN и LEFT JOIN на пользователях и заказах
LEFT JOIN сохраняет левую строку даже без пары, поэтому подходит для поиска нулевых значений.

Задача 10: HAVING и проверка решений

10. Найдите покупателей с оплаченной выручкой больше 2000
  1. WHERE должен оставить только paid-заказы до группировки.
  2. HAVING должен отфильтровать уже посчитанный SUM(o.total).
  3. Ожидаемые строки: Борис 3400 и Вера 2400.
SELECT u.name, SUM(o.total) AS paid_revenue
FROM users AS u
JOIN orders AS o ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY u.id, u.name
HAVING SUM(o.total) > 2000
ORDER BY paid_revenue DESC;
WHERE и HAVING не взаимозаменяемы

WHERE отвечает «какие исходные заказы участвуют», HAVING отвечает «какие готовые группы оставить». Агрегат SUM нельзя перенести в обычный WHERE этого же уровня запроса.

column must appear in GROUP BY

В SELECT есть обычный столбец, который не входит в GROUP BY и не обёрнут агрегатной функцией. Добавьте ключ и имя группы.

Глеб исчез из отчёта

Использован INNER JOIN. Начните запрос с users и примените LEFT JOIN, если нужны пользователи без заказов.

У Глеба count равен 1

После LEFT JOIN функция COUNT(*) считает сохранённую строку пользователя. Используйте COUNT(o.id): NULL-значения она не считает.

Результат правильный, порядок разный

SQL не обещает порядок без ORDER BY. Добавьте явную сортировку, если проверка зависит от расположения строк.

Как честно проверить себя. Запустите запрос на исходных данных, затем добавьте один новый заказ и заранее предскажите, какие строки изменятся. Если можете объяснить результат до запуска, конструкция действительно понятна.
Короткие ответы
В каком порядке писать части SELECT?

Для этих задач: SELECT, FROM, JOIN и ON, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. Не каждая часть обязательна, но их взаимный порядок фиксирован.

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

WHERE фильтрует исходные строки до группировки. HAVING фильтрует группы после GROUP BY и может проверять COUNT, SUM или AVG.

Когда нужен LEFT JOIN?

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

Почему COUNT(*) и COUNT(column) дают разные числа?

COUNT(*) считает строки результата. COUNT(column) считает только строки, где значение указанного столбца не NULL.

Как лучше решать задачи по SQL?

Сначала сформулируйте ожидаемые строки словами, затем напишите запрос, запустите его и проверьте на граничном случае: нуле, NULL, равных значениях или отсутствии связи.

Что решать после этих десяти задач

Повторите базовые команды по материалу SQL: SELECT, INSERT, UPDATE и DELETE, а для локальных экспериментов используйте SQLite для начинающих. Затем добавьте третью таблицу products и попробуйте связать её с orders.

SQL нужен не только backend-разработчикам: в маршруте QA-инженера он используется для проверки данных и поиска причин ошибки. В Кодике перепишите каждое решение без подсказки и сохраните запросы как маленькое портфолио практики.

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты