Каждый день одно и то же. Открываешь клиент базы данных, чтобы что-то проверить, посчитать или найти. И снова пишешь почти тот же SELECT
, что и вчера, с тем же WHERE
и JOIN
. Знакомо?
SQL в большинстве случаях не требует сложные 100-строчные запросы с вложенными подзапросами на три уровня глубины. Чаще всего нам нужны простые, отточенные и, главное, эффективные конструкции.
В этой статье я собрал 7 таких запросов-«рабочих лошадок». Это не какой-то там справочник, а готовая шпаргалка для реальных задач.
Прежде чем перейти к запросам, запомните главное правило любого UPDATE или DELETE: всегда сначала пишите SELECT с теми же условиями WHERE.
Серьёзно, всегда. Это убережёт вас от случайного изменения или удаления не тех данных. Один мой знакомый в начале карьеры однажды чуть не отправил в архив несколько гигабайт production-данных из-за неверного условия. С тех пор он, да и я тоже делаем так:
-- Шаг 1: Сначала СМОТРИМ, что будем обновлять/удалять
SELECT * FROM orders WHERE status = 'test';
-- Шаг 2: Убедившись, что выборка корректна, заменяем на UPDATE/DELETE
DELETE FROM orders WHERE status = 'test';
Теперь, когда этот критически важный ритуал усвоен, перейдём к тем самым «рабочим лошадкам».
❯ 1. Ежедневный мониторинг
Как обычно, первым делом с утра нам нужно проверить, что система жива, данные поступают, ничего не сломалось ночью. Без ORDER BY
и LIMIT
можно случайно выгрузить всю таблицу и подвесить клиент, чего явно нам не хотелось бы.
SELECT id, status, created_at, amount
FROM latest_transactions
WHERE status = 'processed'
ORDER BY created_at DESC
LIMIT 10;
Ключевая привычка — всегда использовать ORDER BY
для детерминированности и LIMIT
для безопасности.
Допустим, вы увидели, что данные поступают. Что дальше? Обычно следом просят свестить итоги и подготовить отчет.
❯ 2. Агрегация данных
Самый частый запрос для любого отчёта. Посчитать количество, сумму, среднее значение по группам. Здесь на первый план выходят GROUP BY
и агрегатные функции.
SELECT
user_id,
COUNT(*) AS total_orders,
SUM(amount) AS total_revenue
FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY user_id
HAVING SUM(amount) > 1000
ORDER BY total_revenue DESC;
Считает выручку по каждому пользователю с начала года, но показывает только «VIP-клиентов», принесших больше 1000 единиц. Запомните: WHERE
фильтрует строки догруппировки, а HAVING
— после.
Готовя отчеты, но вы вдруг замечаете аномалии: некоторые цифры не сходятся. Есть подозрение на дубликаты.
❯ 3. Охота за дубликатами
Дубликаты — вечная головная боль. Они появляются из-за ошибок в логике приложения, сбоев и багов. Их нужно уметь быстро находить. Спасет GROUP BY
и HAVING
.
SELECT
email,
COUNT(*) AS duplicate_count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
Находит все email-адреса, которые встречаются в таблице пользователей больше одного раза. Это отправная точка для «зачистки».
Дубликаты найдены. Чтобы принять осмысленное решение по ним, нужно больше информации. Пора заглянуть в связанные таблицы.
❯ 4. Объединение данных из нескольких таблиц
Данные в нормализованной базе всегда разбросаны. Чтобы получить полную картину, нужны JOIN
-ы.
SELECT
u.first_name,
u.last_name,
o.order_date,
o.total_amount,
p.product_name
FROM users u
INNER JOIN orders o ON u.id = o.user_id
LEFT JOIN products p ON o.product_id = p.id
WHERE u.country = 'Germany'
ORDER BY o.order_date DESC;
Главное — не запутаться в их типах.
Их всего 3:INNER JOIN
- только совпадающие записи с обеих сторон
LEFT JOIN
- все записи из левой таблицы + совпадения справа
RIGHT JOIN
- все записи из правой таблицы + совпадения слева
Объединив данные, вы обнаруживаете сложную закономерность: нужно найти записи, которые соответствуют условию на основе других данных.
❯ 5. Запросы в запросах
Когда простых условий WHERE
уже недостаточно, на помощь приходят подзапросы. Они позволяют вложить один запрос в другой для сложной фильтрации.
SELECT *
FROM products
WHERE price > (
SELECT AVG(price)
FROM products
WHERE category_id = 5
)
AND category_id = 5;
Находит все товары в категории 5, цена которых выше средней по этой же категории. Идеально для поиска аномалий и выбросов.
Проанализировав данные, вы понимаете, что пора не просто смотреть, а что-то менять. Самое опасное — модификация данных.
❯ 6. Модификация данных с умом
Вот мы и дошли до самого опасного — UPDATE
и DELETE
. Здесь правило только одно: семь раз отмерь, один раз выполни. Всегда начинай с SELECT
.
-- ШАГ 1: Сначала смотрим, что будем менять
SELECT *
FROM users
WHERE last_login < '2024-01-01'
AND active = true;
-- ШАГ 2: Только затем выполняем UPDATE (и лучше в транзакции!)
BEGIN;
UPDATE users
SET active = false
WHERE last_login < '2024-01-01'
AND active = true;
-- Смотрим, что получилось, и только потом COMMIT или ROLLBACK
COMMIT;
Данные обновлены. Теперь нужно проанализировать результат в разрезе времени или групп, не схлопывая строки. Обычного GROUP BY
уже мало.
❯ 7. Оконные функции
Финальный аккорд — мощные оконные функции (OVER
, PARTITION BY
). Они позволяют делать агрегацию без группировки, сохраняя все исходные строки.
SELECT
product_id,
sale_date,
daily_revenue,
SUM(daily_revenue) OVER (
PARTITION BY product_id
ORDER BY sale_date
) AS cumulative_revenue
FROM sales
ORDER BY product_id, sale_date;
Для каждого товара рассчитывает нарастающий итог выручки по дням. В отличие от GROUP BY
, мы видим каждую продажу, а не только итог.
Вот и все. Этих 7 запросов хватит, чтобы уверенно чувствовать себя в 90% рабочих ситуаций с SQL. Они покрывают весь цикл работы с данными: от ежедневного мониторинга до сложной аналитики и аккуратной модификации.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
? Читайте также:
➤ Технологии из прошлого: телетайп. Разбираемся в устройстве, воскрешаем старый аппарат
➤ Болезнь Крона, осы-паразиты и «больной нытик из Цюриха»: что стоит за главной сценой «Чужого»
Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
Комментарии (0)
Akina
16.09.2025 11:06Без указания конкретной СУБД (а ещё лучше - и её версии) такая статья ещё до прочтения теряет половину применимости.
Ежедневный мониторинг
Такой запрос пригоден только в том случае, если имеется индекс по
created_at
(или индекс, где это поле является префиксом). Иначе можно неплохо нагрузить сервер, тем более что таблица транзакций редко бывает компактной. Если в таблице имеется первичный индекс по полю автоинкремента или UUIDv7, сортировку лучше выполнять по нему.Их всего 3 (речь о различных JOIN)
Огорчу. Их немножко больше. FULL, CROSS... и даже LATERAL.
Запросы в запросах
CTE для этой цели подходят лучше.
Модификация данных с умом
Это модификация с небольшим количеством ума. Малейший ляп при составлении второго запроса - и привет, проблемы. Особенно если запрос многотабличный, а условий много. Тут надо не только проверять все условия, но ещё и следить за стопроцентным наличием, и при этом за отсутствием интерференции, у алиасов - от таких ляпов, между прочим, даже копипаст не спасает.
Чтобы вероятность ошибиться была меньше, можно идти более аккуратным путём. Пишется представление, а не SELECT-запрос. Потом SELECT-ом к представлению проверяем, что собираемся модифицировать. И затем пишем запрос на модификацию/удаление, выполняя операцию из другой копии таблицы и связывая её с представлением по первичному ключу.
-----
А вообще, как по мне, то столь простые запросы лучше как раз писать "на лету", а не заниматься сохранением шаблонов. Но это моё личное мнение...
Sau
Мои запросы, которые хоть и не решают 90 % всех проблем на работе, а всего лишь нужны для случаев которые я не помню так сразу.
Синтаксис UPDATE JOIN для MySQL:
UPDATE tableA a
JOIN tableB b ON a.a_id = b.a_id
JOIN tableC c ON b.b_id = c.b_id
SET b.val = a.val+c.val
WHERE a.val > 10 AND c.val > 10;
SELECT без таблицы для Firebird
SELECT 'abc' AS abc, 123 AS def, '20110101' AS dt FROM RDB$DATABASE
Выбор строк с максимальной датой
SELECT * FROM ddd
JOIN (SELECT ddd2.da_id, MAX(ddd2.timestamp) dts, count(*) c
FROM ddd ddd2 GROUP BY ddd2.da_id ) ddd3 ON ddd3.da_id = ddd.da_id AND ddd3.dts = ddd.timestamp
Удаление временной таблицы в MS SQL
IF OBJECT_ID('tempdb..#temp_table') is not null DROP TABLE #temp_table
Описание полей временной таблицы в MS SQL
SELECT * FROM Tempdb.Sys.Columns WHERE Object_ID = Object_ID('tempdb..#temp_table')
А также синтаксис WITH RECURSIVE.