Иногда самые коварные ошибки в данных не вызывают резкого всплеска на графике — они тихо и равномерно искажают всю картину. Именно такой случай произошёл при подготовке нового дашборда на основе старого SQL-запроса.
Что случилось: анатомия тихой ошибки
Две таблицы джойнились по ID заявки. В левой таблице было 5 млн строк, у которых ID равнялся нулю — и это было штатной ситуацией для данной системы. В правой таблице существовала ровно одна запись с нулевым ID, которую по-хорошему следовало сразу отфильтровывать.
В результате все 5 млн «нулевых» строк получили данные из этой единственной записи. Ошибка не дала никакого резкого пика: строки с нулевым ID равномерно распределялись по всем датам, и визуально метрика выглядела правдоподобно.
Как ошибку всё-таки заметили
Несоответствие выявил коллега, который обратил внимание на провалы в одном из разрезов по выходным дням. Поскольку реальные заявки связаны с графиком работы сотрудников, в выходные их почти нет. Но аномальные 5 млн строк — с одними и теми же присвоенными данными — распределялись равномерно, включая выходные, что и создало видимый дисбаланс.
Это хороший пример того, почему доменное знание о бизнес-процессе помогает находить баги там, где статистика молчит.
Почему NULL в JOIN особенно опасен
В SQL NULL — это отсутствие значения, а не число ноль (0). Однако числовой ноль (0) в роли ID ведёт себя схожим образом: если в обеих таблицах есть строки с ключом = 0, JOIN их соединит. При этом:
- Одна «мусорная» запись в правой таблице может размножиться на миллионы строк в левой.
- Результирующий набор данных не содержит явных дубликатов — строки выглядят уникальными.
- Агрегаты (SUM, AVG, COUNT) меняются незаметно, без выбросов.
- Равномерное распределение по времени маскирует проблему на временны́х графиках.
Что сделать на практике
- Перед написанием JOIN проверяйте распределение ключей: SELECT join_key, COUNT(*) FROM table GROUP BY join_key ORDER BY COUNT(*) DESC — нули и пустые значения сразу будут видны.
- Фильтруйте технические нули в источнике или в CTE до основного JOIN, а не после.
- Добавляйте в запрос явное условие WHERE t.id IS NOT NULL AND t.id <> 0, если нулевой ID не несёт смысловой нагрузки.
- При ревью старых запросов под новые дашборды всегда проверяйте логику ключей — требования к данным могли измениться.
- Включайте в приёмочную проверку дашборда срезы по выходным, праздникам и другим «нетипичным» периодам — они часто выявляют аномалии, которые будние дни скрывают.
Вывод
NULL и числовой ноль в роли ключа JOIN — зона повышенного риска. Ошибка может не проявиться в сводных числах и не дать пика на графике, но исказить всю аналитику. Регулярная проверка распределения ключей и фильтрация технических нулей до джойна — простая привычка, которая экономит часы отладки.
