Какие проверки выполняешь для данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Проверки качества данных (Data Validation)
Качество данных — основание любого аналитического вывода. Я всегда следую принципу: "Не доверяй данным, пока не проверишь". Вот мой набор проверок, которые я выполняю для каждого датасета.
1. Проверки на полноту (Completeness)
Проблема: Пропущенные значения (NULL) могут исказить выводы.
-- Проверка: какой процент NULL в каждой колонке
SELECT
'users' as table_name,
COUNT(*) as total_rows,
COUNT(*) FILTER (WHERE id IS NULL) as null_id,
COUNT(*) FILTER (WHERE email IS NULL) as null_email,
COUNT(*) FILTER (WHERE created_at IS NULL) as null_created_at,
COUNT(*) FILTER (WHERE name IS NULL) as null_name,
ROUND(100.0 * COUNT(*) FILTER (WHERE email IS NULL) / NULLIF(COUNT(*), 0), 2) as null_email_pct,
ROUND(100.0 * COUNT(*) FILTER (WHERE created_at IS NULL) / NULLIF(COUNT(*), 0), 2) as null_created_at_pct
FROM users;
-- Результат: если null_email_pct > 1%, это красный флаг
Алертинг: NULL > 5% обычно означает проблему в источнике данных (сломанный API, bad ETL).
2. Проверки на дубликаты (Uniqueness)
Проблема: Дубликаты приводят к переоценке метрик.
-- Проверка: есть ли дубликаты в primary key
SELECT
user_id,
COUNT(*) as occurrence_count
FROM users
GROUP BY user_id
HAVING COUNT(*) > 1
LIMIT 10;
-- Если результат непустой — есть дубликаты!
-- Глубже: проверка дубликатов по email (должен быть уникален)
SELECT
email,
COUNT(*) as user_count
FROM users
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY user_count DESC;
-- Проверка: могут ли быть "скрытые" дубликаты (разные ID, но тот же email)
WITH email_groups AS (
SELECT
email,
ARRAY_AGG(DISTINCT id) as user_ids,
COUNT(DISTINCT id) as unique_user_ids
FROM users
WHERE email IS NOT NULL
GROUP BY email
)
SELECT * FROM email_groups WHERE unique_user_ids > 1 LIMIT 10;
Рекомендация: Дубликатов быть не должно. Если есть — нужно разобраться в причине.
3. Проверки на диапазон (Range Validation)
Проблема: Невозможные значения (цена < 0, возраст 200 лет, дата в будущем).
-- Проверка: цены не могут быть отрицательными
SELECT
order_id,
amount,
created_at
FROM orders
WHERE amount < 0 -- это ошибка!
LIMIT 10;
-- Проверка: даты заказа в будущем
SELECT
COUNT(*) as future_orders_count
FROM orders
WHERE created_at > CURRENT_TIMESTAMP;
-- Проверка: возраст пользователя в допустимом диапазоне
SELECT
user_id,
EXTRACT(YEAR FROM AGE(birth_date)) as age,
birth_date
FROM users
WHERE EXTRACT(YEAR FROM AGE(birth_date)) < 13
OR EXTRACT(YEAR FROM AGE(birth_date)) > 120;
-- Проверка: количество покупок (может ли быть отрицательным?)
SELECT
user_id,
SUM(CASE WHEN quantity < 0 THEN quantity ELSE 0 END) as negative_qty_total
FROM order_items
GROUP BY user_id
HAVING SUM(CASE WHEN quantity < 0 THEN quantity ELSE 0 END) != 0
LIMIT 5;
Рекомендация: Каждая колонка имеет бизнес-диапазон. Выход за пределы — это ошибка.
4. Проверки на консистентность (Consistency)
Проблема: Данные в разных таблицах не согласуются между собой.
-- Проверка: все order_id из order_items должны быть в orders
SELECT COUNT(*) as orphan_items
FROM order_items oi
LEFT JOIN orders o ON oi.order_id = o.id
WHERE o.id IS NULL; -- если результат > 0, есть проблема
-- Проверка: сумма в order должна равняться сумме items
WITH order_totals AS (
SELECT
o.id as order_id,
o.amount as order_total,
SUM(oi.quantity * oi.unit_price) as items_total,
ABS(o.amount - SUM(oi.quantity * oi.unit_price)) as difference
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.amount
)
SELECT * FROM order_totals
WHERE difference > 0.01 -- допускаем небольшие различия из-за rounded
LIMIT 10;
-- Проверка: дата создания заказа не может быть позже даты обновления
SELECT COUNT(*) as inconsistent_dates
FROM orders
WHERE created_at > updated_at;
5. Проверки на внешние зависимости (Foreign Key Validation)
-- Проверка: все user_id должны существовать в таблице users
SELECT COUNT(*) as invalid_user_refs
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;
-- Проверка в обе стороны: есть ли пользователи без заказов (это нормально, но полезно знать)
SELECT COUNT(*) as users_without_orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
6. Проверки на аномалии (Outliers)
Проблема: Экстремальные значения могут быть как реальными (VIP клиент), так и ошибками.
-- Проверка: выявляем выбросы используя IQR метод
WITH order_stats AS (
SELECT
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY amount) as q1,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY amount) as q3,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY amount) -
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY amount) as iqr
FROM orders
WHERE amount > 0
)
SELECT
o.order_id,
o.amount,
CASE
WHEN o.amount < (qs.q1 - 1.5 * qs.iqr) THEN 'LOW_OUTLIER'
WHEN o.amount > (qs.q3 + 1.5 * qs.iqr) THEN 'HIGH_OUTLIER'
ELSE 'NORMAL'
END as anomaly_flag
FROM orders o
CROSS JOIN order_stats qs
WHERE o.amount < (qs.q1 - 1.5 * qs.iqr)
OR o.amount > (qs.q3 + 1.5 * qs.iqr)
LIMIT 20;
-- Проверка: резкие скачки в ежедневные метрики
WITH daily_metrics AS (
SELECT
DATE(created_at) as date,
COUNT(*) as daily_orders,
SUM(amount) as daily_revenue,
LAG(SUM(amount)) OVER (ORDER BY DATE(created_at)) as prev_day_revenue
FROM orders
GROUP BY DATE(created_at)
)
SELECT
date,
daily_orders,
daily_revenue,
prev_day_revenue,
ROUND(100.0 * (daily_revenue - prev_day_revenue) / NULLIF(prev_day_revenue, 0), 2) as day_over_day_change_pct
FROM daily_metrics
WHERE ABS(100.0 * (daily_revenue - prev_day_revenue) / NULLIF(prev_day_revenue, 0)) > 50 -- скачок > 50%
ORDER BY date DESC;
7. Проверки на форматирование (Format Validation)
Проблема: Email содержит опечатки, номер телефона неправильного формата.
-- Проверка: email в правильном формате
SELECT
user_id,
email,
CASE
WHEN email NOT LIKE '%@%.%' THEN 'INVALID_FORMAT'
WHEN email LIKE '% %' THEN 'CONTAINS_SPACES'
WHEN email = LOWER(email) AND email LIKE '%@%' THEN 'VALID'
ELSE 'SUSPICIOUS'
END as email_status
FROM users
WHERE email IS NOT NULL
AND email NOT LIKE '%@%.%' -- стандартная проверка
LIMIT 20;
-- Проверка: URL ссылки
SELECT
id,
profile_url
FROM users
WHERE profile_url IS NOT NULL
AND profile_url NOT LIKE 'http://%'
AND profile_url NOT LIKE 'https://%'
LIMIT 10;
8. Проверки на статистическую аномалию (Statistical Tests)
import pandas as pd
from scipy import stats
# Загруженные данные
df = pd.read_sql("SELECT amount FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'", conn)
# Проверка на нормальность распределения (Shapiro-Wilk тест)
statistic, p_value = stats.shapiro(df['amount'])
if p_value < 0.05:
print(f"Распределение НЕ нормальное (p={p_value:.4f})")
else:
print(f"Распределение нормальное (p={p_value:.4f})")
# Проверка на статистический скачок
mean_7d = df['amount'].mean()
std_7d = df['amount'].std()
z_scores = abs((df['amount'] - mean_7d) / std_7d)
outliers = df[z_scores > 3] # значения, которые отстоят на 3 стандартных отклонения
print(f"Выявлено {len(outliers)} потенциальных аномалий")
9. Дашборд для мониторинга качества данных
-- Единая витрина для мониторинга качества
WITH data_quality_checks AS (
SELECT
'users' as table_name,
'NULL_CHECK' as check_type,
ROUND(100.0 * COUNT(*) FILTER (WHERE email IS NULL) / COUNT(*), 2) as metric_value,
CASE WHEN COUNT(*) FILTER (WHERE email IS NULL) > COUNT(*) * 0.01 THEN 'FAIL' ELSE 'PASS' END as status
FROM users
UNION ALL
SELECT
'orders',
'NEGATIVE_AMOUNTS',
COUNT(*)::numeric,
CASE WHEN COUNT(*) > 0 THEN 'FAIL' ELSE 'PASS' END
FROM orders
WHERE amount < 0
UNION ALL
SELECT
'orders',
'ORPHAN_RECORDS',
COUNT(*)::numeric,
CASE WHEN COUNT(*) > 0 THEN 'FAIL' ELSE 'PASS' END
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL
UNION ALL
SELECT
'order_items',
'NULL_CHECK',
ROUND(100.0 * COUNT(*) FILTER (WHERE order_id IS NULL) / COUNT(*), 2),
CASE WHEN COUNT(*) FILTER (WHERE order_id IS NULL) > 0 THEN 'FAIL' ELSE 'PASS' END
FROM order_items
)
SELECT * FROM data_quality_checks ORDER BY status DESC, table_name;
Мой чеклист проверок
✓ Завтрак (перед использованием данных):
- NULL-значения < 1%
- Нет дубликатов в ID
- Нет значений вне диапазона
- Консистентность между таблицами
✓ После получения новых данных:
- Количество записей совпадает с ожиданиями
- Нет резких скачков (день на день > 50%)
- Формат данных корректный
- Временные метки в правильном порядке
✓ При больших изменениях метрик:
- Запросить source данных у инженеров
- Проверить логи ETL на ошибки
- Сравнить с независимым источником (Google Analytics, платежный шлюз)
- Провести root cause analysis
Главный принцип: Не работай с данными, в которых ты не уверен. Лучше потратить час на проверку, чем представить неправильный отчёт в совет директоров.