Гранулярность результата и уникальность ключа
Содержание курса
Проверка не нашла повторов, а отчёт неверен
Успешная проверка ключа через HAVING COUNT(*) > 1 показывает только отсутствие одинаковых ключей в текущей выборке. Сам отчёт при этом может быть ошибочным сразу по нескольким причинам.
Первая причина — неверный состав столбцов группировки. Если требовался отчёт по клиентам, но в GROUP BY добавили дату заказа, каждый день разобьёт клиента на отдельную строку. Проверка по паре (client_id, order_date) не покажет дублей, но гранулярность «один клиент — одна строка» будет нарушена.
Вторая причина — группировка по неуникальному признаку вместо идентификатора. Посмотрим отдельный контрпример: не меняя базовые таблицы, подменим имя клиента 2 с «Борис» на «Анна» через CTE. Если сгруппировать заказы только по имени:
WITH clients_cte(client_id, client_name) AS (
VALUES (1, 'Анна'), (2, 'Анна'), (3, 'Вера')
)
SELECT c.client_name, SUM(o.total) AS total_spent
FROM clients_cte c
JOIN orders o ON o.client_id = c.client_id
GROUP BY c.client_name
ORDER BY c.client_name;
Результат объединит двух разных людей:
| client_name | total_spent |
|---|---|
| Анна | 4500 |
Проверка по client_name не найдёт повторов, но расходы Анны (3000) и второго клиента (1500) сложились в одну строку.
Если же вернуть в группировку ключ client_id, но скрыть его из итогового SELECT:
WITH clients_cte(client_id, client_name) AS (
VALUES (1, 'Анна'), (2, 'Анна'), (3, 'Вера')
)
SELECT c.client_name, SUM(o.total) AS total_spent
FROM clients_cte c
JOIN orders o ON o.client_id = c.client_id
GROUP BY c.client_id, c.client_name
ORDER BY c.client_id;
Запрос вернёт две строки:
| client_name | total_spent |
|---|---|
| Анна | 3000 |
| Анна | 1500 |
Сам по себе пропуск столбца в блоке SELECT группы не объединяет: число строк определил GROUP BY c.client_id, c.client_name.
Третья причина — отсутствие GROUP BY при наличии агрегатной функции. Запрос:
SELECT client_id, SUM(total) AS total_spent
FROM orders;
возвращает ровно одну строку:
| client_id | total_spent |
|---|---|
| 1 | 4500 |
Функция SUM(total) сложила суммы всех трёх заказов компании (4500), а неагрегированный столбец client_id вернул произвольное значение (в данном запуске — 1, но SQLite не гарантирует выбор конкретной строки). Проверка ключа по этой единственной строке покажет ноль дублей, хотя отчёт по клиентам не построен.
Четвёртая причина — неполнота данных при изменении требований. Для исходной задачи «клиенты с покупками» отсутствие Веры (client_id = 3) корректно: у неё нет заказов, и INNER JOIN отсёк её ожидаемо. Но если бизнес-требование меняется на «отчёт по всем клиентам компании», этот же результат становится неверным из-за потери данных:
SELECT c.client_id
FROM clients c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.client_id = c.client_id
)
ORDER BY c.client_id;
Запрос находит Веру (3). Никакая проверка уникальности ключа в готовом отчёте не выявит её пропажу, потому что считает дубли только среди тех строк, которые уже дошли до результата.
