В современных 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"
]).ngroups4.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.
