Типы данных и качество данных

«Расскажите, как вы проверяете качество данных перед анализом» — вопрос, на котором отсеивают половину кандидатов.

Качество данных — это не «нет ошибок», а измеримый набор свойств: полнота, уникальность, валидность, согласованность, актуальность и точность.

Вопрос 1. Какие бывают типы данных и почему это важно аналитику?

Что проверяет интервьюер. Не помните ли вы список типов из документации, а понимаете ли, что тип определяет, какие операции над данными вообще имеют смысл. Считать среднее по почтовым индексам можно технически и нельзя содержательно.

Аналитику удобнее держать в голове не типы СУБД, а шкалы измерения:

ШкалаПримерЧто можно считать
Номинальнаягород, канал привлечениячастоты, мода
Порядковаяоценка 1–5, грейд+ медиана, квантили
Интервальнаядата, температура в °C+ разности, среднее
Абсолютнаявыручка, число заказов+ отношения, суммы, проценты

Отсюда честный ответ на классическую подколку «посчитайте среднее по полю region_id»: среднее по идентификатору бессмысленно, region_id — номинальная шкала, записанная числом. Такие поля в хранилище лучше держать текстом или явно помечать как категориальные.

Вторая половина вопроса — про типы в базе. Три вещи, которые спрашивают чаще всего:

  • Деньги нельзя хранить во FLOAT: 0.1 + 0.2 в двоичной плавающей точке не равно 0.3, и на миллионе строк это превращается в расхождение с бухгалтерией. Нужен DECIMAL/NUMERIC или целые копейки.
  • Дата ≠ строка. Строка '2026-13-01' спокойно ляжет в TEXT, а сортировка '10.03.2026' будет лексикографической и уедет.
  • Часовые пояса. Если события пишутся в UTC, а отчёт строится по московскому дню, суточные метрики сдвинутся на три часа — и «падение трафика ночью» окажется артефактом.

Вопрос 2. Как вы проверяете качество данных?

Сильный ответ — назвать измерения качества и показать, что для каждого есть конкретная проверка. Слабый — сказать «смотрю глазами в Excel».

ИзмерениеПроверка
Полнотадоля NULL и пустых строк по каждой колонке
Уникальностьдубли по бизнес-ключу, а не только по id
Валидностьдиапазоны (возраст 0–120), формат email, домен значений статуса
Согласованностьсумма заказов сходится с суммой строк заказа; нет заказов у несуществующих клиентов
Актуальностьмаксимальная дата в таблице — сегодняшняя, а не позавчерашняя
Точностьсверка с внешним источником: биллинг, 1С, рекламный кабинет

На практике это один SQL-запрос, который прогоняют по каждой новой выгрузке:

CREATE TABLE raw_users (id INTEGER, email TEXT, age INTEGER, revenue REAL);
INSERT INTO raw_users VALUES
 (1,'anna@mail.ru',29,1200.0),
 (2,'boris@mail.ru',NULL,0.0),
 (2,'boris@mail.ru',NULL,0.0),
 (3,'vera@mail.ru',34,NULL),
 (4,'',-5,450.5),
 (5,'gleb@mail.ru',201,300.0);

SELECT
  COUNT(*)                                                   AS rows_total,
  COUNT(DISTINCT id)                                         AS ids_unique,
  SUM(CASE WHEN age IS NULL THEN 1 ELSE 0 END)               AS age_nulls,
  SUM(CASE WHEN revenue IS NULL THEN 1 ELSE 0 END)           AS revenue_nulls,
  SUM(CASE WHEN email IS NULL OR email = '' THEN 1 ELSE 0 END) AS email_bad,
  SUM(CASE WHEN age < 0 OR age > 120 THEN 1 ELSE 0 END)      AS age_impossible
FROM raw_users;

Вывод:

rows_total  ids_unique  age_nulls  revenue_nulls  email_bad  age_impossible
6           5           2          1              1          2

Шесть строк, пять уникальных id — значит есть дубль. Два невозможных возраста (-5 и 201), один пустой email, пропуски в двух колонках. Такой профиль занимает минуту, а спасает от отчёта, который придётся отзывать.

Вопрос 3. Что делать с найденными проблемами?

Тот же профиль на Python — код, который можно запустить прямо здесь:

from collections import Counter

rows = [
    {"id": 1, "email": "anna@mail.ru",    "age": "29"},
    {"id": 2, "email": "boris@mail.ru",   "age": ""},
    {"id": 2, "email": "boris@mail.ru",   "age": ""},
    {"id": 3, "email": "vera(at)mail.ru", "age": "34"},
    {"id": 4, "email": "gleb@mail.ru",    "age": "201"},
]

ids = Counter(r["id"] for r in rows)
print("Дубликаты id:", [i for i, c in ids.items() if c > 1])
print("Пустой age:", sum(1 for r in rows if r["age"] == ""))
print("Битый email:", [r["id"] for r in rows if "@" not in r["email"]])
print("Возраст вне диапазона:",
      [r["id"] for r in rows if r["age"].isdigit() and not 0 < int(r["age"]) < 120])

Вывод:

Дубликаты id: [2]
Пустой age: 2
Битый email: [3]
Возраст вне диапазона: [4]

Главная мысль, которую ждут от кандидата: данные не «чистят молча». Правильный порядок — зафиксировать проблему, оценить её масштаб в процентах, найти причину на стороне источника и только потом решать, чинить в ETL или возвращать поставщику данных. Молчаливое удаление 3% строк однажды окажется удалением всей мобильной аудитории.

Типичные ошибки кандидатов

  • Отвечают «проверяю на NULL» и на этом заканчивают — про дубли, диапазоны и согласованность не вспоминают.
  • Ищут дубли только по техническому id, хотя бизнес-дубль — это одна и та же покупка с разными id.
  • Не спрашивают, что означает NULL в конкретной колонке: «неизвестно» и «ноль» требуют разной обработки.
  • Не проверяют актуальность: отчёт строится, но данные приехали неделю назад.
  • Считают среднее по полям-идентификаторам и категориям, закодированным числами.

Как ответить кратко

«Перед анализом я прогоняю профиль качества по шести измерениям: полнота (доля NULL), уникальность (дубли по бизнес-ключу, не только по id), валидность (диапазоны и форматы), согласованность (сходятся ли связанные таблицы), актуальность (свежая ли максимальная дата) и точность (сверка с источником вроде биллинга). Это один SQL-запрос с COUNT и CASE WHEN. Найденное я не чиню молча: фиксирую долю проблемных строк, ищу причину на стороне источника и согласую правило обработки — иначе можно тихо выкинуть целый сегмент аудитории».

Проверьте себя
1. Почему сумму денег не стоит хранить в поле типа FLOAT?
AFLOAT не поддерживает отрицательные значения
BДвоичная плавающая точка не представляет десятичные дроби точно, и ошибки накапливаются, расходясь с бухгалтерией
CFLOAT занимает больше места, чем DECIMAL
DВ FLOAT нельзя записать больше шести знаков
2. Какое измерение качества данных проверяет запрос, сравнивающий COUNT(*) и COUNT(DISTINCT id)?
AПолноту
BУникальность
CАктуальность
DТочность
3. Что правильнее сделать, обнаружив 3% строк с невозможными значениями?
AМолча удалить их и продолжить анализ — доля мала
BЗафиксировать долю, найти причину на стороне источника и согласовать правило обработки
CЗаполнить их средним по колонке
DИгнорировать: SQL-агрегаты сами справятся