Как посчитать data completeness в SQL

Закрепи формулу data completeness в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать data completeness в Telegram

Зачем 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% после деплоя — что-то отвалилось.

Закрепи формулу data completeness в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать data completeness в Telegram

Алерт при просадке

Сравнение текущей 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-партнёра). Считайте в разрезе.

Связанные темы

FAQ

Что считать полным?

NULL и пустая строка — обычно «пусто». Default values (например, 0, 'unknown') — спорно: формально заполнено, фактически нет.

Какой порог?

99% для обязательных полей, 80%+ для важных, для опциональных — стабильность важнее уровня.

Completeness vs validity?

Completeness = заполнено ли. Validity = заполнено корректно (email формата *@*.*).

Считать на total или на cohort?

На свежей вставке — индикатор регресса. На total — индикатор исторического качества.

Как реагировать на резкий drop?

Откатить деплой, найти трекинг-баг, до фикса показывать предупреждение в дашборде.