В этой статье разберём несколько типичных ошибок и недооценённых приёмов, которые встречаются даже у опытных аналитиков.
1. COUNT(*) vs COUNT(col)
Многие уверены, что COUNT(*), COUNT(1) и COUNT(имя_столбца) — одно и то же. Но это не так.
Предположим, что у нас есть таблица с пользователями, где указаны их имейлы. Если вы напишете:
SELECT COUNT(email) FROM users
то получите количество заполненных имейлов. То есть если у пользователя email равен NULL, он не попадёт в подсчёт. А вот:
SELECT COUNT(*) FROM users
или
SELECT COUNT(1) FROM users
посчитают все строки.
Используйте
COUNT(имя_столбца), только если вам действительно нужно считать непустые значения, а не строки.
2. Лишние поля в GROUP BY
Например, у вас есть таблица с транзакциями клиентов, в которой есть ID клиента, ID транзакции и её размер. Вам нужно считать общее количество транзакций на клиента.
Новички часто не понимают, как работает GROUP BY и какие поля в нём указывать, поэтому часто можно встретить такое:
SELECT client_id, sum(amount) as sum_amount
FROM transactions
GROUP BY client_id, transact_id
Данный скрипт отработает, но вместо одного client_id у вас будет 10 строк (зависит от количества транзакций у клиента). Для того чтобы написать правильно, нужно указывать только те поля, которые есть в SELECT:
SELECT client_id, sum(amount) as sum_amount
FROM transactions
GROUP BY client_id
3. Неверные поля в GROUP BY
Ещё одной частой ошибкой при использовании GROUP BY является использование поля вместо условия. Например:
SELECT client_id, case when start_date >= date’2026-01-01’ then 1 else 0 end as new_client_flag, sum(amount) as sum_amount
FROM transactions
GROUP BY client_id, start_date
Этот скрипт отработает, но сгруппирует неверно. Чтобы правильно сгруппировать строки, нужно использовать всё условие:
SELECT client_id, case when start_date >= date’2026-01-01’ then 1 else 0 end as new_client_flag, sum(amount) as sum_amount
FROM transactions
GROUP BY client_id, case when start_date >= date’2026-01-01’ then 1 else 0 end
4. LIKE без %
На работе нам часто приходится искать данные в текстовых полях. Например, мы отправляем SMS клиентам, но предварительно тестируем их. Заказчик просит нас собрать воронку, поэтому нам нужно исключить тестовые записи.
Если мы напишем:
WHERE name NOT LIKE ‘test’
То получим только строгое соответствие, и как следствие, завышенный верх воронки. Поэтому для полноценного поиска нам нужно указывать:
WHERE name NOT LIKE ‘%test%’
Процент, поставленный до и после искомого слова, позволяет выбрать данные с любым количеством символов до и после.
5. DISTINCT не лечит дубли
Очень частая ошибка: «У меня дубли, добавлю DISTINCT и всё». Но DISTINCT — это не всегда решение. Важно понимать причину дублей и использовать соответствующий метод дедупликации.
Например:
SELECT DISTINCT u.client_id, t.amount
FROM clients u
LEFT JOIN transactions t ON u.client_id = t.client_id
Если у пользователя 10 транзакций, вы получите 10 строк. DISTINCT оставит 10 строк, потому что amount разный.
Правильный подход — агрегировать, суммируя amount:
SELECT u.client_id, SUM(t.amount) AS total_amount
FROM clients u
LEFT JOIN transactions t ON u.client_id = t.client_id
GROUP BY u.client_id
6. Использование SELECT * в продовых запросах
SELECT * — это самый частый запрос, который я пишу, но не всё так просто. Да, на этапе исследования данных это очень удобно. Ты видишь все столбцы и понимаешь структуру таблицы, но в проде SELECT * — это лишние данные, которые нагружают темп и память.
Например, у нас есть таблица с пользователями и транзакциями:
SELECT *
FROM transactions t
JOIN users u ON t.client_id = u.client_id
Сделать SELECT * для изучения и проверки — правильное решение, но записывать такую конструкцию в созданную таблицу нельзя. Если в таблицу users добавят поле с большим текстом — например, выводы ИИ о клиенте — то во-первых, запрос сломается, так как появится новое поле. А во-вторых, если заменили содержимое существующего поля, то запрос внезапно станет в 10 раз тяжелее.
Правильно отбирать только нужные столбцы:
SELECT t.client_id, t.amount, u.segment
FROM transactions t
JOIN users u ON t.client_id = u.client_id
7. Неправильная работа с NULL в условиях
NULL — это не значение, это пустота. И многие забывают, что сравнения с NULL работают иначе, чем сравнение с заполненными полями.
Например, вам нужно отобрать всех клиентов, кроме клиентов с тестовым емейлом. Если написать:
WHERE email <> ‘test@example.com’
То строки, где email = NULL, не попадут в выборку, хотя логически должны. Это частая ошибка, на которой ловят новичков. Чтобы правильно отобрать клиентов, используйте:
WHERE email <> ‘test@example.com’ OR email IS NULL
или
WHERE nvl(email, ‘N/A’) <> ‘test@example.com’
8. Фильтрация после JOIN вместо фильтрации до JOIN
Очень часто нам нужно объединить таблицы и отфильтровать их одновременно. Многие начинающие аналитики делают так:
SELECT *
FROM users u
LEFT JOIN transactions t ON u.client_id = t.client_id
WHERE t.amount > 100
Фильтр превращает LEFT JOIN в INNER JOIN и убивает значения из таблицы users, так как отфильтровывает все данные после объединения. Но пользователи без транзакций вам тоже нужны. Чтобы сделать правильный запрос и ничего не потерять, используйте фильтрацию внутри JOIN:
LEFT JOIN transactions t
ON u.client_id = t.client_id
AND t.amount > 100
Это не только правильно, но и оптимально с точки зрения производительности.
9. Использование HAVING вместо WHERE
Путаница между WHERE и HAVING — это вечная проблема. Запомните, HAVING — это фильтр после группировки. Давайте рассмотрим на примере:
SELECT employee_id, SUM(salary)
FROM salary
GROUP BY employee_id
HAVING SUM(salary) > 1000
В данном запросе результат будет покажет только сотрудников с общей зарплатой более 1000 за весь период. То есть фильтрация будет после агрегации. Но если вам нужны только те сотрудники, которые каждый месяц получают более 1000, то есть условие до агрегации, то вам нужно использовать:
SELECT employee_id, SUM(salary)
FROM salary
GROUP BY employee_id
WHERE salary > 1000
Выглядит похоже, но смысл совершенно разный.
10. Использование UNION вместо UNION ALL
UNION, в отличие от UNION ALL, сверяет все строки и удаляет дубли. Поэтому использование UNION значительно замедляет запрос. Если вам не нужно удалять дубли, то не пишите:
SELECT client_id FROM table1
UNION
SELECT client_id FROM table2
Вместо этого используйте:
SELECT client_id FROM table1
UNION ALL
SELECT client_id FROM table2
