В современных data-процессах ключевую роль играет обеспечение качества данных. Рассмотрим четыре популярных подхода: DBT, SQL, Python (Pandas/SQLAlchemy) и Great Expectations, оценив их эффективность для различных сценариев проверки данных.
Эта статья будет интересна и полезна Data-инженерам, аналитикам данных и специалистам Data Quality для выбора оптимального метода валидации данных в зависимости от стека технологий и сложности бизнес-логики. Материал ориентирован на начинающий уровень подготовки: тем, кто еще не сталкивался системно с инструментами управления качеством данных.
Привет, Хабр! Меня зовут Мария, я Data-инженер в SimbirSoft, и предлагаю для начала немного познакомиться с каждым из вышеперечисленных инструментов.
Содержание статьи
1. Сравнение методов проверки минимальной и максимальной даты
1.1 Реализация в DBT
1.2. Реализация в Python (Pandas)
1.3. Реализация в Python (SQLAlchemy)
1.4. Реализация в чистом SQL
1.5. Реализация в Great Expectations
2. Сравнение методов подсчета общего количества записей в таблице
2.1. Реализация в DBT
2.2. Реализация в Python (Pandas)
2.3 Реализация в Python (SQLAlchemy)
2.4. Реализация в чистом SQL
2.5. Реализация в Great Expectations
3. Сравнение методов проверки пустых значений в таблице
3.1. Реализация в DBT
3.2. Реализация в Python (Pandas)
3.3. Реализация в Python (SQLAlchemy)
3.4. Реализация в чистом SQL
3.5. Реализация в Great Expectations
4. Сравнение методов проверки дубликатов по бизнес-ключу
4.1. Реализация в DBT
4.2. Реализация в Python (Pandas)
4.3. Реализация в Python (SQLAlchemy)
4.4. Реализация в чистом SQL
4.5. Реализация в Great Expectations
5. Сравнение методов проверки качества данных в таблице raw_flight_data
5.1. Реализация в DBT
5.2. Реализация в Python (Pandas)
5.3. Реализация в Python (SQLAlchemy)
5.4. Реализация в чистом SQL (PL/pgSQL)
5.5. Реализация в Great Expectations
Анализ результатов тестирования производительности
1. DBT
DBT (Data Build Tool) — это инструмент для трансформации данных, который позволяет не только строить аналитические модели, но и встроенными средствами проверять их качество. Тестирование в DBT — ключевой механизм обеспечения надежности данных, помогающий выявлять ошибки, аномалии и нарушения бизнес-правил еще на этапе формирования данных.
Плюсы:
Встроенные тесты (unique, not_null).
Кастомные проверки на SQL/Python.
Инкрементальная валидация.
Минусы: Материализация результатов может затянуться.
DBT улучшает Data Quality с помощью встроенных тестов (Generic tests), кастомных тестов (Custom tests, с помощью SQL или Python), схемных тестов (Schema tests), инкрементальной валидации, интеграции с мониторингом.
2. SQL
SQL — это самый быстрый и прямой способ проверки качества данных с помощью прямых запросов и хранимых процедур. Он работает на уровне СУБД без накладных расходов на передачу данных в другие системы.
Плюсы:
Молниеносные запросы.
Не требует сложных setup-ов.
Идеальна для ad-hoc-проверок.
Минусы: Нет встроенного мониторинга.
3. Python (Pandas / SQLAlchemy)
Pandas — библиотека Python для анализа и обработки табличных данных (как «Excel на стероидах»). Используется для чтения, фильтрации и преобразования данных; визуализации и статистического анализа.
SQLAlchemy — библиотека Python для работы с SQL-базами через Python-код. Используется для выполнения SQL-запросов без ручного соединения с БД.
Плюсы:
Pandas — удобен для исследований.
SQLAlchemy — баланс между скоростью SQL и гибкостью Python.
Минусы:
Pandas «задыхается» на 8 млн. строк.
Чанкирование спасает.
Great Expectations — фреймворк для автоматизированной валидации данных (как «пилот для вашего Data Quality»). Он необходим для проверки данных на соответствие правилам, документирования требований к данным, автоматических отчетов при ошибках, интеграции в ETL-пайплайны.
Плюсы:
Готовые «правила знакомств» (expectations) для данных — никаких неожиданностей!
Детализированные отчеты с примерами проблемных строк, статистикой по нарушениям и ссылками на исходные ожидания — легко диагностировать и исправлять ошибки.
Минусы: медленнее некоторых конкурентов.
Контекст исследования
В ходе исследования я применила dbt к реальному набору данных об авиаперелетах (Kaggle Dataset), чтобы оценить его возможности в области Data Quality.
1. Характеристики тестируемых таблиц:
Параметр |
light_data (исходные данные) |
raw_flight_data (DBT-модель) |
Источник данных |
Преобразованная модель на основе flight_data |
|
Количество строк |
8,249,965 |
87,657 (данные за 1 день) |
Количество полей |
27 |
28 |
Период данных |
Полный период |
Данные за 1 день (по searchDate) |
2. Инфраструктура:
СУБД: GreenPlum
ОС: Ubuntu
3. Тестируемые сценарии:
№ |
Сценарий тестирования |
Проверяемые параметры |
Актуальные для Data Quality |
1 |
Минимальная и максимальная бизнес-дата |
Корректность временного периода данных |
Полнота данных |
2 |
Количество записей |
Соответствие ожидаемому объему данных |
Полнота данных |
3 |
Количество записей с пустыми полями |
Заполненность критически важных полей |
Качество данных |
4 |
Количество дубликатов по бизнес-ключу |
Уникальность записей |
Целостность данных |
5 |
Проверка полей в DBT-модели (raw_flight_data) |
Соответствие преобразованных данных требованиям |
Качество трансформации |
4. Методы тестирования.
Для каждого сценария использовались 5 подходов:
№ |
Метод |
Версия |
Описание |
Преимущества |
Ограничения |
1 |
DBT |
DBT-core=1.5.10 DBT-greenplum=1.5.0 DBT-labs/ DBT_utils= 0.8.0 |
Встроенные тесты и материализация таблиц |
Встроенная валидация, документация |
Требует знания YAML-синтаксиса |
2 |
Pandas |
pandas=2.1.4
|
Полная выгрузка в DataFrame (с чанкированием для сценария 3) |
Гибкость анализа |
Высокое потребление памяти
|
3 |
SQLAlchemy |
SQLAlchemy=2.0.41 |
Выполнение SQL-запросов через Python-библиотеку |
Баланс между гибкостью и производительностью |
Требуется знание ORM |
4 |
Чистый SQL (GP) |
PostgreSQL=9.4.24 (Greenplum Database 6.12.0 build dev) |
Напрямую в СУБД |
Максимальная производительность |
Меньше гибкости для сложных проверок |
5 |
Great Expectations |
great-expectations=1.5.2
|
Фреймворк для валидации данных |
Богатые возможности валидации |
Дополнительная настройка |
1. Сравнение методов проверки минимальной и максимальной даты
1.1 Реализация в DBT
{{
config(
materialized='table',
compresstype='zlib'
)
}}
SELECT
MIN(searchdate) AS min_search_date,
MAX(searchdate) AS max_search_date
FROM {{ source('test', 'flight_data') }}
Используется материализация в таблицу с компрессией (zlib для экономии места).
Запрос выполняется непосредственно в DBT, что удобно для документирования и интеграции в пайплайн.
Подходит для регулярного мониторинга временного диапазона данных.
1.2. Реализация в Python (Pandas)
df = pd.read_sql_table(
table_name='flight_data',
con=engine,
schema='test',
columns=['searchdate']
)
min_search_date = df['searchdate'].min()
max_search_date = df['searchdate'].max()
Для снижения ресурсоемкости в dataframe выгружается только 1 колонка.
Простота реализации, но требует больше памяти.
Подходит для единоразовых проверок или анализа в Jupyter-ноутбуках.
1.3. Реализация в Python (SQLAlchemy)
result = conn.execute(text(f"SELECT MIN(searchdate) AS min_search_date, MAX(searchdate) AS max_search_date FROM {DB_SCHEMA}.flight_data;"))
row = result.fetchone()
Запрос выполняется на стороне СУБД, что эффективнее полной выгрузки.
Используется text() для RAW SQL, что дает гибкость, но требует аккуратности с инъекциями.
Оптимален для скриптов, где нужны только агрегированные результаты.
1.4. Реализация в чистом SQL
SELECT
MIN(searchdate) AS min_search_date,
MAX(searchdate) AS max_search_date
FROM test.flight_data;
Самый быстрый метод, так как выполняется напрямую в СУБД.
Нет накладных расходов на ORM или выгрузку в Python.
Лучший выбор для ad-hoc-проверок в SQL-клиенте.
1.5. Реализация в Great Expectations
expectation_date_range = gxe.ExpectColumnValuesToBeBetween(
column="searchdate",
min_value=datetime(2022, 4, 16),
max_value=datetime(2022, 5, 4)
)
res = batch.validate(expectation_date_range)
Проверяет, что все даты попадают в ожидаемый диапазон (валидация против эталона).
Генерация детализированного отчета о валидации.
Полезно для регулярного мониторинга качества данных.
2. Сравнение методов подсчета общего количества записей в таблице
2.1. Реализация в DBT
config(
materialized='table',
compresstype='zlib'
)
}}
SELECT
count(1)
FROM {{ source('test', 'flight_data') }}
2.2. Реализация в Python (Pandas)
df = pd.read_sql_table(
table_name='flight_data',
con=engine,
schema='test',
columns=['legid']
)
count = df['legid'].count()
2.3 Реализация в Python (SQLAlchemy)
result = conn.execute(text(f"SELECT count(1) FROM {DB_SCHEMA}.flight_data;"))
row = result.fetchone()
2.4. Реализация в чистом SQL
SELECT count(1) FROM test.flight_data;
2.5. Реализация в Great Expectations
expectation_count = gxe.ExpectTableRowCountToBeBetween(min_value=2, max_value=9000000)
res = batch.validate(expectation_count)
Проверка соответствия количества строк ожидаемому диапазону.
Позволяет установить границы допустимых значений.
3. Сравнение методов проверки пустых значений в таблице
Особенности реализации:
Все SQL-методы используют одинаковую логику подсчета.
Great Expectations дает наиболее детализированную информацию.
Pandas требует чанкирования для больших таблиц.
3.1. Реализация в DBT
{{
config(
materialized='table',
compresstype='zlib'
)
}}
select
sum(case
when legid is null
or searchdate is null
/* ... остальные 25 полей ... */
or segmentscabincode is null
then 1 else 0
end) as is_null
from {{ source('test', 'flight_data') }}
Полноценный SQL-запрос с проверкой всех 27 полей.
3.2. Реализация в Python (Pandas)
null_counts = pd.Series()
chunk_size = 50000
for chunk in pd.read_sql_table(
'flight_data',
engine,
schema='test',
chunksize=chunk_size
):
chunk_null_counts = chunk.isnull().sum()
null_counts = null_counts.add(chunk_null_counts, fill_value=0)
Построчная обработка данных чанками по 50,000 записей.
Постепенное суммирование null-значений по всем столбцам.
Требует значительных ресурсов памяти и времени.
3.3. Реализация в Python (SQLAlchemy)
result = conn.execute(text(f"""
select
sum(case
when legid is null
or searchdate is null
/* ... остальные 25 полей ... */
or segmentscabincode is null
then 1 else 0
end) as is_null
from {DB_SCHEMA}.flight_data;"""))
row = result.fetchone()
3.4. Реализация в чистом SQL
select
sum(case
when legid is null
or searchdate is null
/* ... остальные 25 полей ... */
or segmentscabincode is null
then 1 else 0
end) as is_null
from test.flight_data;
3.5. Реализация в Great Expectations
columns = ["legid", "searchdate", ...] # все 27 полей
n = 0
for col in columns:
print(f'{col}:')
expectation_null = gxe.ExpectColumnValuesToNotBeNull(column=col)
res = batch.validate(expectation_null)
if res.success == False:
print(f"{col} is null: {res.result["unexpected_count"]}")
n += res.result["unexpected_count"]
Поочередная проверка каждого столбца.
Детализированный отчет по каждому полю.
Возможность установки различных правил валидации.
4. Сравнение методов проверки дубликатов по бизнес-ключу
4.1. Реализация в DBT
{{
config(
materialized='table',
compresstype='zlib'
)
}}
select coalesce(sum(cnt), count(1)) from
(select
count(1) as cnt
from {{ source('test', 'flight_data') }}
group by legid, searchdate
having count(1) > 1
) t
Использование COALESCE для корректной обработки случая без дубликатов.
Группировка по бизнес-ключу (legid + searchdate).
4.2. Реализация в Python (Pandas)
df = pd.read_sql_table(
table_name='flight_data',
con=engine,
schema='test',
columns=['legid', 'searchdate']
)
grouped = df.groupby([
"legid",
"searchdate"
])
duplicate_keys = grouped.filter(lambda x: len(x) > 1)
duplicate_count = duplicate_keys.groupby([
"legid",
"searchdate"
]).ngroups
4.3. Реализация в Python (SQLAlchemy)
result = conn.execute(text(f"""
select coalesce(sum(cnt), count(1)) from
( select
count(1) as cnt
from {DB_SCHEMA}.flight_data
group by legid, searchdate
having count(1) > 1 ) t;"""))
row = result.fetchone()
4.4. Реализация в чистом SQL
select coalesce(sum(cnt), count(1)) from
(select
count(1) as cnt
from test.flight_data
group by legid, searchdate
having count(1) > 1
) t;
4.5. Реализация в Great Expectations
expectation_dbl = gxe.ExpectCompoundColumnsToBeUnique(
column_list=["legid", "searchdate"]
)
res = batch.validate(expectation_dbl)
5. Сравнение методов проверки качества данных в таблице raw_flight_data
5.1. Реализация в DBT
models:
- name: raw_flight_data
columns:
- name: legid
tests:
- not_null
- name: flightdate
tests:
- not_null
- DBT_utils.expression_is_true:
expression: " >= searchdate"
- name: startingairport
tests:
- not_null
- DBT_utils.expression_is_true:
expression: " != destinationairport"
- name: totalfare
tests:
- not_null
- DBT_utils.expression_is_true:
expression: " >= basefare"
Декларативный подход через YAML-конфигурацию.
Использование встроенных тестов (not_null) и кастомных проверок через dbt_utils.
Интеграция в DBT-пайплайн с автоматическим выполнением.
Легко читаемая и поддерживаемая конфигурация.
5.2. Реализация в Python (Pandas)
df = pd.read_sql_table(
table_name='raw_flight_data',
con=engine,
schema=DB_SCHEMA,
columns=['legid', 'flightdate', 'searchdate', 'startingairport', 'destinationairport', 'basefare', 'totalfare']
)
# Тест 1: legid не должен содержать NULL
assert df['legid'].isnull().sum() == 0
# Тест 2: flightdate >= searchdate
assert (df['flightdate'] >= df['searchdate']).all()
# Тест 3: startingairport != destinationairport
assert (df['startingairport'] != df['destinationairport']).all()
# Тест 4: totalfare >= basefare
assert (df['totalfare'] >= df['basefare']).all()
Полная выгрузка данных в DataFrame.
Простые проверки через assert.
Позволяет гибко обрабатывать результаты.
Ресурсоемкий подход для больших таблиц.
5.3. Реализация в Python (SQLAlchemy)
# Тест 1: Проверка NULL в legid
null_legid = conn.execute(text(f"""
SELECT COUNT(*) FROM {DB_SCHEMA}.raw_flight_data WHERE legid IS NULL
""")).scalar()
assert null_legid == 0
# Тест 2: flightdate < searchdate
invalid_dates = conn.execute(text(f"""
SELECT COUNT(*) FROM {DB_SCHEMA}.raw_flight_data
WHERE flightdate < searchdate
""")).scalar()
assert invalid_dates == 0
# Тест 3: startingairport = destinationairport
same_airports = conn.execute(text(f"""
SELECT COUNT(*) FROM {DB_SCHEMA}.raw_flight_data
WHERE startingairport = destinationairport
""")).scalar()
assert same_airports == 0
# Тест 4: totalfare < basefare
invalid_fares = conn.execute(text(f"""
SELECT COUNT(*) FROM {DB_SCHEMA}.raw_flight_data
WHERE totalfare < basefare
""")).scalar()
assert invalid_fares == 0
Выполнение SQL-запросов через SQLAlchemy.
Проверки происходят на стороне СУБД.
Более эффективен чем Pandas для больших таблиц.
5.4. Реализация в чистом SQL (PL/pgSQL)
CREATE OR REPLACE FUNCTION validate_flight_data()
RETURNS TABLE (
test_name TEXT,
error_count INTEGER,
error_details TEXT
) AS $$
BEGIN
-- 1. Проверка NULL в legid
RETURN QUERY
SELECT
'legid_not_null'::text AS test_name,
COUNT(1)::int AS error_count,
'Найдены записи с NULL в legid'::text AS error_details
FROM raw.raw_flight_data
WHERE legid IS NULL;
-- 2. Проверка flightdate >= searchdate
RETURN QUERY
SELECT
'flightdate_after_searchdate'::text AS test_name,
COUNT(*)::int AS error_count,
'Найдены записи где flightdate раньше searchdate'::text AS error_details
FROM raw.raw_flight_data
WHERE flightdate < searchdate;
-- 3. Проверка startingairport != destinationairport
RETURN QUERY
SELECT
'airports_different'::text AS test_name,
COUNT(*)::int AS error_count,
'Найдены записи с одинаковыми startingairport и destinationairport'::text AS error_details
FROM raw.raw_flight_data
WHERE startingairport = destinationairport;
-- 4. Проверка totalfare >= basefare
RETURN QUERY
SELECT
'totalfare_ge_basefare'::text AS test_name,
COUNT(*)::int AS error_count,
'Найдены записи где totalfare меньше basefare'::text AS error_details
FROM raw.raw_flight_data
WHERE totalfare < basefare;
RETURN;
END;
$$ LANGUAGE plpgsql;
Хранимая процедура в PostgreSQL.
Возвращает детализированный отчет об ошибках.
Максимальная производительность.
Сложность в поддержке и версионировании.
5.5. Реализация в Great Expectations
# Тест 1: legid не должен содержать NULL
expectation = gxe.ExpectColumnValuesToNotBeNull(column='legid')
assert batch.validate(expectation).success
# Тест 2: flightdate >= searchdate
expectation = gxe.ExpectColumnPairValuesAToBeGreaterThanB(column_A='flightdate', column_B='searchdate')
assert batch.validate(expectation).success
# Тест 3: startingairport != destinationairport
expectation = gxe.ExpectColumnPairValuesToBeEqual(column_A='startingairport', column_B='destinationairport')
assert not batch.validate(expectation).success
# Тест 4: totalfare >= basefare
expectation = gxe.ExpectColumnPairValuesAToBeGreaterThanB(column_A='totalfare', column_B='basefare')
assert batch.validate(expectation).success
Использование готовых проверок (Expectations).
Детализированные отчеты о валидации.
Возможность интеграции в пайплайны.
Требует настройки конфигурации.
Анализ результатов тестирования производительности:
|
|
DBT |
Python/pandas |
Python/sqlalchemy |
Greate Expentations |
SQL |
||||||
Таблица |
№ |
Время, сек |
Время считывания в df, сек. |
Время выполнения проверки, сек. |
Всего, сек. |
Время подключения к БД, сек. |
Время выполнения проверки, сек. |
Всего, сек. |
Время получения батча, сек. |
Время выполнения проверки, сек. |
Всего, сек. |
Время, сек |
flight_data |
1 |
7,29 |
30,13 |
0,03 |
30,16 |
0,19 |
1,58 |
1,77 |
0,1 |
3,64 |
3,74 |
1,14 |
flight_data |
2 |
7,28 |
30,13 |
0,24 |
30,37 |
0,19 |
3,53 |
3,72 |
0,1 |
1,35 |
1,45 |
1,26 |
flight_data |
3 |
7,21 |
327,7 |
24,74 |
352,44 |
0,19 |
2,65 |
2,84 |
0,1 |
130,44 |
130,54 |
2,27 |
flight_data |
4 |
8,67 |
30,13 |
193,05 |
223,18 |
0,19 |
4,59 |
4,78 |
0,1 |
21,97 |
22,07 |
4,97 |
raw_flight_data |
5 |
0,57 |
- |
- |
0,85 |
0,19 |
0,1 |
0,29 |
0,1 |
0,51 |
0,61 |
0,15 |
Общее время: |
|
31,02 |
418,09 |
218,06 |
637 |
0,19 |
12,45 |
12,64 |
0,1 |
157,91 |
158,01 |
9,8 |
Наиболее эффективные методы:
Чистый SQL (9.8 сек. суммарно) — абсолютный лидер по скорости.
SQLAlchemy (12.64 сек.) — минимальная разница с чистым SQL благодаря эффективному использованию соединения.
DBT (31.02 сек.) — дополнительные затраты на материализацию данных.
Наименее эффективные методы:
Pandas (637 сек.) — 95% времени тратится на загрузку данных в DataFrame.
Great Expectations (158 сек.) — высокие накладные расходы на валидацию.
Сравнение DBT и чистого SQL:
Разница в 3,2 раза объясняется материализацией результатов.
Без материализации время выполнения было бы сопоставимо с чистым SQL.
Особые случаи:
Проверка пустых значений в Pandas заняла 352 сек. (в 155 раз медленнее SQL).
Поиск дубликатов в Great Expectations — 22 сек. против 5 сек. в SQL.
В конечном итоге DBT показал себя эффективным для проверки дубликатов и NULL-значений (встроенные тесты unique и not_null), инкрементального тестирования (например, для raw_flight_data). Pandas требовал больше ресурсов для обработки больших таблиц (особенно при чанкировании), поэтому его стоит использовать только при необходимости последующей обработки данных в Python и последующей визуализации. SQLAlchemy и чистый SQL оказались быстрее для простых агрегаций (например, min/max даты). Great Expectations добавил детализированные отчеты, но увеличил время выполнения.
Рекомендации по выбору инструмента:
Метод |
Преимущества |
Недостатки |
Подходящие сценарии |
Производительность |
Поддержка сложных проверок |
DBT |
Интеграция в пайплайн, документация |
Требует инфраструктуры DBT, ограниченный набор тестов |
Регулярные проверки в ETL |
Высокая |
Средняя (через макросы) |
Pandas |
Простота в использовании и отладки, гибкость |
Ресурсоемкость |
Исследовательский анализ в Jupyter |
Низкая |
Высокая |
SQLAlchemy |
Баланс скорости и гибкости |
Нужно экранировать запросы |
Разовые проверки, высоконагруженные запросы |
Высокая |
Высокая |
Чистый SQL |
Максимальная скорость |
Ручное выполнение, сложность поддержки |
Быстрых ad-hoc-проверок, высоконагруженные запросы |
Очень высокая |
Высокая |
Great Expectations |
Валидация против эталонов, готовые проверки, детальные отчеты |
Сложность настройки, дополнительные зависимости |
Комплексные проверки качества с детализированными отчетами |
Средняя |
Очень высокая |
Заключение
Мы с вами рассмотрели четыре популярных подхода к обеспечению качества данных: классический SQL, DBT, библиотеку Pandas и фреймворк Great Expectations.
Для production-решений с большими объемами данных оптимальны SQL-подходы (чистый SQL/SQLAlchemy). Они позволяют выполнять проверки на стороне базы данных, минимизируя передачу данных и снижая нагрузки на вычислительные ресурсы. DBT сохраняет баланс между удобством и производительностью для регулярных задач. Благодаря встроенным возможностям для тестирования и управляемой структуре моделей, DBT отлично подходит для регулярных задач проверки данных в рамках современных ELT-процессов. Pandas и Great Expectations следует использовать осознанно, учитывая их накладные расходы.
Выбор подхода всегда зависит от контекста: объема данных, требований к скорости, зрелости инфраструктуры и уровня команды. Надеемся, что этот обзор поможет вам выбрать подходящий инструмент и уверенно начать строить надежные и качественные data-процессы.
Спасибо за внимание!
Больше авторских материалов для backend-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.
Комментарии (2)

savostin
22.12.2025 22:05Замените PostgreSQL на Clickhouse и будет счет вестить на миллисекунды ибо 8 млн. записей для него - мелочь.
savostin
Имхо у вас SQL = SQLAlchemy