Как посчитать data completeness в SQL
Содержание:
Зачем completeness
Data completeness = доля непустых значений в поле. На дашбордах, в ML-фичах и в отчётности всегда есть допущение «поле заполнено». Если null-rate ползёт вверх, метрика начинает врать без явного сигнала. Стандартный фрейм для DQ: completeness + uniqueness + freshness + validity + consistency.
Доля заполненности
SELECT
COUNT(*) AS total_rows,
COUNT(email) AS non_null,
COUNT(email)::NUMERIC * 100 / NULLIF(COUNT(*), 0) AS completeness_pct
FROM users
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days';COUNT(column) сам игнорирует NULL — это удобнее, чем COUNT(*) FILTER (WHERE column IS NOT NULL). Для пустых строк — отдельно фильтруйте column <> ''.
Completeness по полям
SELECT
'email' AS field,
COUNT(email)::NUMERIC * 100 / COUNT(*) AS completeness_pct
FROM users
UNION ALL
SELECT
'phone',
COUNT(phone)::NUMERIC * 100 / COUNT(*)
FROM users
UNION ALL
SELECT
'avatar_url',
COUNT(avatar_url)::NUMERIC * 100 / COUNT(*)
FROM users
UNION ALL
SELECT
'country',
COUNT(country)::NUMERIC * 100 / COUNT(*)
FROM users;Один запрос на 5–10 ключевых полей — это уже базовый DQ-дашборд.
Динамика во времени
Падающая completeness указывает на регрессию (трекинг сломали или формат данных изменился):
SELECT
DATE_TRUNC('day', created_at)::DATE AS day,
COUNT(*) AS rows_inserted,
COUNT(email)::NUMERIC * 100 / NULLIF(COUNT(*), 0) AS email_completeness_pct,
COUNT(country)::NUMERIC * 100 / NULLIF(COUNT(*), 0) AS country_completeness_pct
FROM users
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE_TRUNC('day', created_at)
ORDER BY day;Если 95% утром стало 30% после деплоя — что-то отвалилось.
Алерт при просадке
Сравнение текущей completeness с baseline 7-дневным:
WITH current_day AS (
SELECT COUNT(email)::NUMERIC * 100 / NULLIF(COUNT(*), 0) AS pct
FROM users
WHERE created_at >= CURRENT_DATE - INTERVAL '1 day'
),
baseline AS (
SELECT COUNT(email)::NUMERIC * 100 / NULLIF(COUNT(*), 0) AS pct
FROM users
WHERE created_at BETWEEN CURRENT_DATE - INTERVAL '8 days'
AND CURRENT_DATE - INTERVAL '1 day'
)
SELECT
c.pct AS today_pct,
b.pct AS baseline_pct,
c.pct - b.pct AS drop_pp,
CASE
WHEN b.pct - c.pct > 10 THEN 'CRITICAL: > 10pp DROP'
WHEN b.pct - c.pct > 5 THEN 'WARNING: 5-10pp DROP'
ELSE 'ok'
END AS verdict
FROM current_day c CROSS JOIN baseline b;Частые ошибки
Ошибка 1. Считать COUNT(*) - COUNT(NULL).
COUNT(NULL) всегда 0. Правильно — COUNT(column).
Ошибка 2. Игнорировать пустые строки.
В Postgres '' ≠ NULL. Если в трекинге пустая строка значит «не заполнено» — добавьте OR column = ''.
Ошибка 3. Считать на all-time. DQ-метрика должна быть на свежем периоде (1-7 дней). Иначе старые данные затмят свежий регресс.
Ошибка 4. Один порог для всех полей.
email — обязательное (порог 99%). avatar_url — опциональное (норма 30%). Калибруйте.
Ошибка 5. Не учитывать сегментацию. Completeness может падать только в одном channel (например, после API-партнёра). Считайте в разрезе.
Связанные темы
- Как посчитать data freshness в SQL
- Как посчитать data quality score в SQL
- Как посчитать duplicate rate в SQL
- Как посчитать data staleness в SQL
FAQ
Что считать полным?
NULL и пустая строка — обычно «пусто». Default values (например, 0, 'unknown') — спорно: формально заполнено, фактически нет.
Какой порог?
99% для обязательных полей, 80%+ для важных, для опциональных — стабильность важнее уровня.
Completeness vs validity?
Completeness = заполнено ли. Validity = заполнено корректно (email формата *@*.*).
Считать на total или на cohort?
На свежей вставке — индикатор регресса. На total — индикатор исторического качества.
Как реагировать на резкий drop?
Откатить деплой, найти трекинг-баг, до фикса показывать предупреждение в дашборде.