GROUP BY и HAVING: считать и отсеивать группы
Одиночная улика молчит. Она говорит, когда повторяется. Не «заходил ли он в лавку» — а «сколько раз он туда заходил за месяц». Вопрос меняется с «кто» на «сколько раз».
GROUP BY сворачивает строки в группы по общему признаку, COUNT считает, сколько их в каждой.
SELECT person_id, COUNT(*) AS visits
FROM receipts
GROUP BY person_id;| person_id | visits |
|---|---|
| 104 | 1 |
| 201 | 2 |
| 307 | 11 |
Один визит или одиннадцать — где проходит граница?
Один визит — шум. Одиннадцать за месяц в лавку, где сам ничего не покупал, — уже узор. Отсечь редких помогает не WHERE, а HAVING: WHERE режет строки до того, как они сгруппированы, HAVING — уже сами группы, после подсчёта.
SELECT person_id, COUNT(*) AS visits
FROM receipts
GROUP BY person_id
HAVING COUNT(*) >= 5;«WHERE спрашивает про человека. HAVING спрашивает про его привычку.»
CASE считает по условию прямо внутри одного прохода — не три отдельных запроса, а один срез сразу на несколько корзин.
SELECT person_id,
SUM(CASE WHEN hour < 12 THEN 1 ELSE 0 END) AS утром,
SUM(CASE WHEN hour >= 18 THEN 1 ELSE 0 END) AS вечером
FROM receipts
GROUP BY person_id;Почему в отчёте сходится не всё?
Капкан здесь тихий и въедливый: COUNT(колонка) считает не строки, а непустые значения этой колонки. Если у части визитов не заполнена графа «сумма», COUNT(сумма) их попросту не досчитает — и «одиннадцать визитов» на бумаге превратятся в «семь», хотя человек приходил все одиннадцать раз.
COUNT(*) -- все строки, включая пустые графы
COUNT(amount) -- только те, где сумма вообще указана«Никто не солгал в отчёте. Отчёт просто не считал то, что не было записано. Это не ложь. Это дыра — и она страшнее лжи, потому что выглядит честно.»
На работе так ловят фрод: не одна подозрительная сделка, а частота — кто повторяет один и тот же манёвр слишком часто для случайности. Одна встреча ничего не значит. Узор — значит всегда.
Одиночная улика молчит и в архиве. Откройте дело, где виновного выдаёт не поступок, а его повторяемость.