SQL17 февраля 2026 г.2.17K

JOIN по NULL в SQL: как нули ломают дашборд

Коротко

JOIN по нулевому ID незаметно присвоил данные из одной записи пяти миллионам строк — ошибка без пика на графике, которую выдал лишь провал по выходным. Разбираем механику и способы защиты.

Иногда самые коварные ошибки в данных не вызывают резкого всплеска на графике — они тихо и равномерно искажают всю картину. Именно такой случай произошёл при подготовке нового дашборда на основе старого 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 — зона повышенного риска. Ошибка может не проявиться в сводных числах и не дать пика на графике, но исказить всю аналитику. Регулярная проверка распределения ключей и фильтрация технических нулей до джойна — простая привычка, которая экономит часы отладки.

Частые вопросы

Чем отличается NULL от нуля (0) в SQL при использовании в JOIN?+

NULL означает отсутствие значения; два поля NULL не считаются равными, поэтому строки с NULL-ключами в стандартном INNER JOIN не соединятся. Числовой ноль (0) — полноценное значение, и JOIN по нему работает как обычно: все строки с key=0 из левой таблицы сопоставятся со всеми строками с key=0 из правой.

Как проверить наличие нулей или NULL в ключевом столбце перед JOIN?+

Выполните SELECT join_key, COUNT(*) AS cnt FROM your_table GROUP BY join_key ORDER BY cnt DESC. Нулевые и NULL-значения окажутся в верхней части списка, если они многочисленны. Дополнительно: SELECT COUNT(*) FROM your_table WHERE join_key IS NULL OR join_key = 0.

Почему ошибка равномерного присвоения данных незаметна на временно́м графике?+

Если «мусорные» строки равномерно распределены по всем датам, они добавляют к каждому периоду одинаковый сдвиг. Линия на графике сдвигается вверх, но остаётся гладкой, без аномальных пиков или провалов — именно поэтому такую ошибку трудно поймать без знания бизнес-контекста.

Как правильно отфильтровать технические нули перед JOIN?+

Лучше всего изолировать фильтрацию в CTE или подзапросе до основного JOIN: WITH clean_right AS (SELECT * FROM right_table WHERE id IS NOT NULL AND id <> 0) SELECT ... FROM left_table l JOIN clean_right r ON l.id = r.id. Это делает намерение явным и не позволяет нулям «просочиться» в результат.

👍2518🙏3🥰2
Читать оригинал в Telegram
Там можно оставить реакцию и написать комментарий
✈️ Открыть
🥰
Валерия Смирнова
Senior BI-аналитик в Авито · @mozzalerra
Сотрудничество →