Два человека могут учить SQL одинаковое время, но один вырастет в 3 раза быстрее другого. Разница — в привычках.
1. Читать план выполнения запроса
Большинство новичков никогда не смотрят EXPLAIN. Опытные специалисты запускают его автоматически для любого нетривиального запроса.
EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id
ORDER BY orders DESC
LIMIT 10;
Вы увидите:
- Сколько строк обработал запрос
- Использовался ли индекс
- Где тратится больше всего времени
Начните смотреть EXPLAIN хотя бы раз в день — через месяц интуиция по запросам сильно вырастет.
2. Писать запросы пошагово
Привычка новичков: написать весь запрос сразу и исправлять ошибки.
Привычка опытных: строить запрос слоями.
-- Шаг 1: убедиться что данные правильные
SELECT * FROM orders WHERE created_at >= '2024-01-01' LIMIT 5;
-- Шаг 2: добавить агрегацию
SELECT user_id, SUM(amount) FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY user_id LIMIT 5;
-- Шаг 3: добавить JOIN
SELECT u.name, SUM(o.amount) AS total
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at >= '2024-01-01'
GROUP BY u.id, u.name
ORDER BY total DESC LIMIT 10;
На каждом шаге вы видите промежуточный результат и понимаете что происходит.
3. Проверять NULL-случаи
Запрос работает — но правильно ли? NULL — главная причина неочевидных ошибок.
После написания запроса задайте себе вопросы:
- Есть ли NULL в колонках которые я фильтрую?
- LEFT JOIN — я учитываю строки где правая часть NULL?
- NOT IN — есть ли NULL в подзапросе?
-- Перед финальным запросом: проверить NULL
SELECT COUNT(*) FROM users WHERE phone IS NULL;
SELECT COUNT(*) FROM orders WHERE user_id IS NULL;
Эта минута проверки экономит часы дебага.
4. Использовать CTE вместо вложенных подзапросов
Вложенный подзапрос — это как вложенные скобки в математике. Читается с трудом, отлаживается сложнее.
-- Плохо: что здесь происходит?
SELECT name FROM (
SELECT user_id, name FROM (
SELECT u.id AS user_id, u.name, COUNT(o.id) AS cnt
FROM users u LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id
) t WHERE cnt > 5
) t2 ORDER BY name;
-- Хорошо: читается как текст
WITH user_order_counts AS (
SELECT u.id, u.name, COUNT(o.id) AS orders_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
),
active_users AS (
SELECT id, name FROM user_order_counts WHERE orders_count > 5
)
SELECT name FROM active_users ORDER BY name;
Разбивайте сложные запросы на CTE — ваш коллега (и вы через месяц) скажут спасибо.
5. Тестировать на маленьких данных
Перед запуском тяжёлого запроса на всей таблице — проверьте логику на маленьком срезе.
-- Добавить LIMIT 100 для проверки логики
SELECT u.name, SUM(o.amount) AS total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= '2024-01-01'
GROUP BY u.id
ORDER BY total DESC
LIMIT 100; -- убрать после проверки
Это избавляет от ситуации «запрос висит 20 минут а я не уверен что результат правильный».
Бонус: комментируйте нетривиальные запросы
-- Считаем только первый заказ каждого пользователя (cohort analysis)
WITH first_orders AS (
SELECT
user_id,
MIN(created_at) AS first_order_at -- дата первого заказа
FROM orders
GROUP BY user_id
)
Через 3 месяца вы не вспомните зачем написали MIN(created_at). Комментарий сэкономит 10 минут.
Эти привычки не появятся за один день. Но если сознательно применять их каждый раз — через месяц они станут автоматическими, и ваш SQL выйдет на другой уровень.