Bezdepbonusu7Программирование: статьи и ответы

Основы SQL

SQL запросы с примерами для начинающих: SELECT, JOIN, GROUP BY

Редакция Bezdepbonusu77 мин чтения

SQL запросы проще понять на живых примерах, поэтому вся статья построена на одной базе из двух таблиц. Вы пройдете путь от простого SELECT до JOIN, GROUP BY и HAVING и разберете ошибки, на которых спотыкаются новички.

Серверная стойка с базой данных для выполнения SQL запросов
Фото: Marcel Douwe Dekker, CC BY-SA 3.0, Wikimedia Commons
Содержание
  1. Учебная база
  2. SELECT и WHERE
  3. Сортировка
  4. JOIN
  5. GROUP BY
  6. HAVING
  7. INSERT, UPDATE, DELETE
  8. Практика
  9. Частые вопросы

Учебная база: две таблицы для примеров

Все SQL запросы в статье работают на одной маленькой базе из двух таблиц: клиенты и заказы. Код подходит для PostgreSQL, MySQL и SQLite. Самый быстрый способ попробовать без установки сервера - SQLite: он встроен в Python, а еще есть онлайн-песочницы вроде sqliteonline и db-fiddle.

CREATE TABLE users (
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  city  TEXT,
  age   INTEGER
);

CREATE TABLE orders (
  id       INTEGER PRIMARY KEY,
  user_id  INTEGER REFERENCES users(id),
  amount   INTEGER,
  created  DATE
);

INSERT INTO users (id, name, city, age) VALUES
  (1, 'Анна', 'Москва', 24),
  (2, 'Борис', 'Казань', 31),
  (3, 'Вера', 'Москва', 19),
  (4, 'Глеб', NULL, 42);

INSERT INTO orders (id, user_id, amount, created) VALUES
  (1, 1, 1500, '2026-09-01'),
  (2, 1, 700,  '2026-09-15'),
  (3, 2, 3200, '2026-10-02'),
  (4, 3, 450,  '2026-10-10');

Обратите внимание: у Глеба нет ни одного заказа и не указан город. Эти пробелы специально оставлены, на них хорошо видна разница между видами JOIN и поведение NULL.

SELECT: выборка данных и фильтр WHERE

SELECT - главная команда SQL. Она говорит базе, какие столбцы вернуть и из какой таблицы.

SELECT name, city FROM users;
SELECT * FROM users;                      -- все столбцы
SELECT name, age + 1 AS next_age FROM users;

Звездочка удобна, чтобы быстро посмотреть таблицу, но в коде приложения лучше перечислять столбцы явно: запрос не сломается, если в таблицу добавят новое поле, и база не будет гонять лишние данные. AS задает псевдоним, под ним столбец придет в результате.

Фильтрация строк

WHERE оставляет только строки, которые подходят под условие.

SELECT name FROM users WHERE city = 'Москва';
SELECT name FROM users WHERE age BETWEEN 20 AND 35;
SELECT name FROM users WHERE city IN ('Москва', 'Казань');
SELECT name FROM users WHERE name LIKE 'А%';
SELECT name FROM users WHERE city IS NULL;

Последняя строка - частая ловушка. Условие city = NULL не вернет ничего, потому что NULL означает "неизвестно" и не равен даже самому себе. Для проверки пустоты есть только IS NULL и IS NOT NULL.

ОператорЧто делаетПример
=, <>Равно, не равноcity <> 'Казань'
>, <=Сравнениеage >= 18
AND, OR, NOTЛогикаage > 20 AND city = 'Москва'
BETWEENДиапазон включительноamount BETWEEN 500 AND 2000
INСовпадение со спискомid IN (1, 3)
LIKEШаблон: % любые символы, _ один символname LIKE '_ера'
IS NULLПроверка на пустое значениеcity IS NULL

ORDER BY, LIMIT и DISTINCT

Строки в таблице не имеют гарантированного порядка. Если порядок важен, задайте его явно.

SELECT name, age FROM users ORDER BY age DESC;
SELECT name, age FROM users ORDER BY city, age;
SELECT * FROM orders ORDER BY amount DESC LIMIT 2;
SELECT DISTINCT city FROM users;

DESC сортирует по убыванию, ASC (по умолчанию) по возрастанию. Сортировать можно по нескольким столбцам: сначала по первому, внутри одинаковых значений по второму. LIMIT ограничивает число строк, вместе с ORDER BY это классический способ получить топ-N. В MS SQL Server вместо LIMIT пишут SELECT TOP 2, в PostgreSQL работает и стандартный вариант FETCH FIRST 2 ROWS ONLY. DISTINCT убирает повторы.

JOIN: как объединить таблицы

Данные о клиенте и его заказах лежат в разных таблицах, связь между ними - столбец orders.user_id. JOIN склеивает строки по такому условию.

SELECT u.name, o.amount, o.created
FROM users u
JOIN orders o ON o.user_id = u.id;

Буквы u и o - короткие псевдонимы таблиц, чтобы не писать имена целиком. Просто JOIN означает INNER JOIN: в результат попадут только клиенты, у которых есть заказы. Глеба там не будет.

SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

LEFT JOIN возвращает всех клиентов из левой таблицы. Для Глеба столбец amount будет NULL. На этом построен популярный вопрос с собеседований "найди клиентов без заказов":

SELECT u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
Тип JOINЧто вернетКогда нужен
INNER JOINТолько строки с совпадением в обеих таблицахКлиенты и их заказы
LEFT JOINВсе строки левой таблицы и совпадения справа или NULLВсе клиенты, даже без заказов
RIGHT JOINВсе строки правой таблицыРедко, обычно переписывают через LEFT
FULL JOINВсе строки обеих таблицСверка двух списков
CROSS JOINКаждая строка с каждойГенерация комбинаций

FULL JOIN поддерживается в PostgreSQL и в SQLite начиная с версии 3.39, а в MySQL его нет, там объединяют LEFT и RIGHT JOIN через UNION. Если забыть условие ON или ошибиться в нем, получится декартово произведение: 4 клиента на 4 заказа дадут 16 строк вместо 4. Это одна из самых частых ошибок новичков.

GROUP BY и агрегатные функции

Агрегатные функции считают одно значение по множеству строк: COUNT, SUM, AVG, MIN, MAX. Без группировки они работают по всей таблице:

SELECT COUNT(*) AS total, SUM(amount) AS revenue FROM orders;

GROUP BY делит строки на группы и считает агрегат для каждой. Например, сколько клиентов в каждом городе:

SELECT city, COUNT(*) AS users_count
FROM users
GROUP BY city;

Строки с пустым городом соберутся в отдельную группу NULL. А вот сумма заказов по каждому клиенту, где пригодятся сразу JOIN и GROUP BY:

SELECT u.name,
       COUNT(o.id)   AS orders_count,
       SUM(o.amount) AS total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY total DESC;

Правило, на котором спотыкаются все: в SELECT вместе с агрегатами можно указывать только столбцы из GROUP BY. Запрос SELECT name, city, COUNT(*) FROM users GROUP BY city PostgreSQL отклонит с ошибкой, потому что непонятно, чье имя показать для группы.

Обратите внимание на COUNT(o.id) вместо COUNT(*). Для Глеба LEFT JOIN дает одну строку с NULL, и COUNT(*) насчитал бы ему один заказ. COUNT(столбец) пропускает NULL и вернет честный ноль.

HAVING: фильтр после группировки

WHERE фильтрует строки до группировки, HAVING - группы после нее. Поэтому условие на агрегат пишется только в HAVING.

SELECT user_id, SUM(amount) AS total
FROM orders
WHERE created >= '2026-09-01'
GROUP BY user_id
HAVING SUM(amount) > 1000;

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

ШагЧасть запросаЧто происходит
1FROM, JOINСобираются исходные строки
2WHEREОтбрасываются лишние строки
3GROUP BYСтроки объединяются в группы
4HAVINGОтбрасываются лишние группы
5SELECTВычисляются столбцы и псевдонимы
6ORDER BYСортировка
7LIMITОбрезка результата

Отсюда понятно, почему псевдоним из SELECT нельзя использовать в WHERE: на шаге 2 его еще нет. А в ORDER BY можно, сортировка идет после SELECT.

INSERT, UPDATE и DELETE: изменение данных

Кроме чтения, SQL умеет добавлять, менять и удалять строки.

INSERT INTO users (id, name, city, age) VALUES (5, 'Дина', 'Пермь', 27);
UPDATE users SET city = 'Самара' WHERE id = 4;
DELETE FROM orders WHERE amount < 500;

Перед UPDATE и DELETE сначала выполните SELECT с тем же WHERE и посмотрите, какие строки попадут под изменение. UPDATE или DELETE без WHERE затронет всю таблицу, и откатить это можно только из бэкапа или внутри транзакции. В рабочих базах оборачивайте опасные изменения в BEGIN; ... COMMIT;, а если результат не понравился, выполняйте ROLLBACK;.

Как тренироваться и где SQL пригодится

  1. Запустите учебную базу из первого раздела в SQLite или онлайн-песочнице.
  2. Решите задачи: средний возраст клиентов по городам; клиент с самой большой суммой заказов; число заказов по месяцам; города, где больше одного клиента.
  3. Переходите к тренажерам с готовыми базами: sql-ex.ru, задачи SQL на LeetCode и Stepik-курсы с автопроверкой.
  4. Свяжите SQL с языком программирования: в Python база SQLite подключается модулем sqlite3 из стандартной библиотеки.
import sqlite3
con = sqlite3.connect("shop.db")
rows = con.execute(
    "SELECT name FROM users WHERE city = ?", ("Москва",)
).fetchall()
print(rows)

Знак вопроса - параметр запроса. Никогда не подставляйте пользовательский ввод в SQL через f-строки: так появляются SQL-инъекции. SQL спрашивают почти на любом собеседовании, в том числе на позицию Python junior, а начать изучать сам Python поможет гайд Python с нуля. Если работаете с API, пригодится и статья о формате JSON. Все материалы по базам данных - в теге SQL.

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

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

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

Чем INNER JOIN отличается от LEFT JOIN?

INNER JOIN возвращает только строки с совпадением в обеих таблицах. LEFT JOIN возвращает все строки левой таблицы, а там, где совпадения справа нет, подставляет NULL.

Почему условие = NULL не работает?

NULL означает неизвестное значение, и любое сравнение с ним дает неизвестный результат, а не истину. Для проверки используйте IS NULL или IS NOT NULL.

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

Подойдут SQLite на своем компьютере, онлайн-песочницы вроде db-fiddle, тренажер sql-ex.ru и бесплатные курсы на Stepik с автоматической проверкой задач.

Какую СУБД выбрать для изучения SQL?

Для старта хватит SQLite: ничего не нужно настраивать. Для работы чаще всего пригодится PostgreSQL, его популярность в российских вакансиях высокая. Базовый синтаксис SELECT, JOIN и GROUP BY у них почти одинаковый.

Источники

SQLPython

Похожие материалы

Версии программ и команды проверены на дату публикации. Если что-то изменилось, сверяйтесь с официальной документацией. Нашли неточность? Напишите на admin@bezdepbonusu7.win.