Руководства STG/ODS/DDS/DM сверены с действующими SQL и DAG, ожидания учебных заглушек связаны с ТЗ. Самопроверка заданий отделена от проверок разработки и исторических планов. Для dim_routes закреплено обновление описательных полей только у открытых версий. SQL, DAG, DDL и настройки стенда не менялись. Проверки: make lint; make test (4 passed, 14 skipped); карта; 292 локальные ссылки; маршрут чтения и карта в Chromium. Живой ETL не запускался.
145 lines
9.9 KiB
Markdown
145 lines
9.9 KiB
Markdown
# Загрузка ODS → DDS: `bookings_to_gp_dds`
|
||
|
||
DAG наполняет измерения и собирает `dds.fact_flight_sales`.
|
||
Одна строка факта соответствует сегменту билета: `(ticket_no, flight_id)`.
|
||
У одного билета может быть несколько таких строк.
|
||
Сборка показана [на карте](design/architecture-map.html#node=dds.fact_flight_sales)
|
||
и в [разборе SQL](design/reading_the_pipeline.md#fact-flight-sales).
|
||
Карту открывайте [локально](design/db_schema.md#как-открыть-карту).
|
||
|
||
## Перед запуском
|
||
|
||
Дождитесь успешных STG и ODS. Таблицы DDS должны быть созданы через
|
||
`bookings_dds_ddl` или общий `make ddl-gp`.
|
||
Затем в Airflow UI запустите `bookings_to_gp_dds` целиком.
|
||
DDS читает состояние ODS; передавать `stg_batch_id` ему не нужно.
|
||
|
||
| Объект | Что уже выполняется на `main` |
|
||
|---|---|
|
||
| `dim_calendar` | Заполнение датами с 2016-01-01 по 2030-12-31, только если таблица пуста |
|
||
| `dim_airports` | Обновление изменившихся атрибутов и вставка новых аэропортов с постоянными SK |
|
||
| `dim_tariffs` | Добавление новых классов обслуживания из `ods.segments` |
|
||
| `fact_flight_sales` | Обновление существующих сегментов и вставка новых пар `(ticket_no, flight_id)` |
|
||
| `dim_airplanes`, `dim_passengers`, `dim_routes` | Загрузки и DQ пока содержат `SELECT 1;`; требования находятся в [части 2 ТЗ](assignment/analyst_spec.md#часть-2-dds-слой-detailed-data-store--измерения) |
|
||
|
||
До выполнения заданий три студенческих измерения остаются пустыми.
|
||
Готовый факт при этом загружается: отсутствующие ключи измерений сохраняются
|
||
как `NULL`, чтобы можно было изучить цепочку до написания собственного SQL.
|
||
|
||
## Порядок задач
|
||
|
||
В [DAG](../airflow/dags/bookings_to_gp_dds.py) сначала выполняется
|
||
`load_dds_dim_calendar → dq_dds_dim_calendar`. Затем:
|
||
|
||
1. Параллельно могут загружаться `dim_airports`, `dim_airplanes`, `dim_tariffs`
|
||
и `dim_passengers`; у каждого измерения своя следующая DQ-задача.
|
||
2. `load_dds_dim_routes` ждет `dq_dds_dim_airports` и `dq_dds_dim_airplanes`,
|
||
затем выполняется `dq_dds_dim_routes`.
|
||
3. `load_dds_fact_flight_sales` ждет DQ всех пяти измерений после календаря.
|
||
4. `dq_dds_fact_flight_sales → finish_dds_summary` завершают запуск.
|
||
|
||
Это порядок задач Airflow. Источники строк и поля соединений показаны
|
||
на карте отдельно от него.
|
||
|
||
## Как работают готовые загрузки
|
||
|
||
[dim_calendar_load.sql](../sql/dds/dim_calendar_load.sql) проверяет
|
||
`NOT EXISTS (SELECT 1 FROM dds.dim_calendar LIMIT 1)`. Если есть хотя бы одна
|
||
строка, скрипт не вставляет даты. Повторный запуск не пересоздает календарь
|
||
и не дополняет частично заполненную таблицу.
|
||
[Его DQ](../sql/dds/dim_calendar_dq.sql) проверяет также покрытие дат рейсов ODS.
|
||
|
||
[dim_airports_load.sql](../sql/dds/dim_airports_load.sql) сохраняет SK
|
||
существующего аэропорта, обновляет изменившиеся атрибуты через
|
||
`IS DISTINCT FROM` и вставляет новые бизнес-ключи.
|
||
[dim_tariffs_load.sql](../sql/dds/dim_tariffs_load.sql) только добавляет
|
||
недостающие значения `fare_conditions`.
|
||
Метки измененных строк относятся к текущему запуску DDS;
|
||
календарь не содержит `_load_id` и `_load_ts`.
|
||
|
||
В [fact_flight_sales_load.sql](../sql/dds/fact_flight_sales_load.sql)
|
||
есть две операции:
|
||
|
||
- `UPDATE` меняет цену, место, признак посадки и метки загрузки, если эти
|
||
бизнес-поля изменились. SK измерений остаются прежними.
|
||
- `INSERT` собирает данные из ODS и измерений для пар
|
||
`(ticket_no, flight_id)`, которых еще нет в факте.
|
||
|
||
Факт не отбирает дельту по HWM. Скрипт читает ODS и сопоставляет бизнес-ключи
|
||
с уже записанными строками.
|
||
|
||
Аэропорты вылета и прилета находятся через `ods.routes → dim_airports`.
|
||
Из ODS выбирается строка с наибольшим `validity` для `route_no`; сортировка
|
||
идет по TEXT, дата рейса в этом отборе не участвует.
|
||
`route_sk` определяется отдельно: по дате вылета в полуоткрытом интервале
|
||
`[valid_from, valid_to)` измерения `dim_routes`. Из найденной версии берется
|
||
код модели для поиска `airplane_sk`.
|
||
[Разбор соединений](design/reading_the_pipeline.md#fact-joins) показывает,
|
||
какие строки сохраняет `LEFT JOIN` и что происходит при отсутствии измерения.
|
||
|
||
## Учебные измерения
|
||
|
||
Начните с [моделей самолетов](assignment/analyst_spec.md#21-ddsdim_airplanes-scd1)
|
||
и [пассажиров](assignment/analyst_spec.md#22-ddsdim_passengers-scd1), затем
|
||
переходите к [версиям маршрута](assignment/analyst_spec.md#23-ddsdim_routes-scd2).
|
||
Поля, hashdiff и алгоритм SCD2 определены в ТЗ.
|
||
|
||
У маршрута изменение версионируемых атрибутов создает новую строку.
|
||
Описательные поля из других измерений обновляются только у открытых версий
|
||
(`valid_to IS NULL`), без новой версии; закрытые версии сохраняют свои значения.
|
||
Это правило также записано в [алгоритме ТЗ](assignment/analyst_spec.md#алгоритм-scd2-пошагово).
|
||
|
||
После реализации измерений выполните
|
||
[пересчет факта и витрин](assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений).
|
||
Обычный повторный запуск DDS не заполнит SK в старых строках факта,
|
||
поскольку его `UPDATE` эти поля не меняет.
|
||
|
||
## Проверка результата
|
||
|
||
В `make gp-psql` выполните:
|
||
|
||
```sql
|
||
SELECT COUNT(*) FROM dds.dim_calendar;
|
||
SELECT COUNT(*) FROM dds.dim_airports;
|
||
SELECT COUNT(*) FROM dds.dim_tariffs;
|
||
|
||
SELECT
|
||
(SELECT COUNT(*) FROM dds.fact_flight_sales) AS fact_rows,
|
||
(SELECT COUNT(*) FROM ods.segments) AS segment_rows;
|
||
|
||
SELECT ticket_no, flight_id, departure_airport_sk, arrival_airport_sk,
|
||
route_sk, airplane_sk, passenger_sk, price, is_boarded
|
||
FROM dds.fact_flight_sales
|
||
ORDER BY ticket_no, flight_id
|
||
LIMIT 10;
|
||
```
|
||
|
||
На подготовленном стенде готовые измерения и факт непусты.
|
||
`fact_rows` должен точно совпасть с `segment_rows`.
|
||
[Готовая DQ факта](../sql/dds/fact_flight_sales_dq.sql) также ищет дубли
|
||
по паре ключей и проверяет обязательные поля.
|
||
|
||
На учебной ветке `NULL` в `passenger_sk`, `route_sk` и `airplane_sk`
|
||
вызывает только `NOTICE`. `NULL` в `tariff_sk` вызывает ошибку;
|
||
для строк с отсутствующим аэропортом и для `calendar_sk` допустима доля
|
||
не выше 1%. Эти послабления не заменяют проверку заполнения ключей после задания.
|
||
|
||
Свои `*_dq.sql` реализуйте по ТЗ и
|
||
[справочнику DQ](reference/dq_taxonomy.md). Для дополнительной проверки DDS
|
||
используйте `validate_dds` в `bookings_validate`, соблюдая
|
||
[порядок запуска и восстановления](assignment/README.md#запуск-и-проверка).
|
||
Далее переходите к [DM](bookings_to_gp_dm.md).
|
||
|
||
## Если загрузка не прошла
|
||
|
||
- Нет `dds.*`: выполните `bookings_dds_ddl`.
|
||
- DQ календаря сообщает о непокрытых датах: сравните даты вылета ODS
|
||
с диапазоном и содержимым `dim_calendar`. Повторный запуск загрузки
|
||
непустого календаря не заполнит пропуски.
|
||
- Число строк факта отличается от ODS: проверьте ключи билета, бронирования
|
||
и рейса, затем соединения в `fact_src`. Потеря строки на `INNER JOIN`
|
||
и размножение строк из-за нескольких подходящих версий дают разные причины
|
||
одного расхождения.
|
||
- Ошибка в вашей DQ маршрутов: проверьте открытые версии, интервалы и hashdiff
|
||
по ТЗ. Успешная заглушка `SELECT 1;` этих свойств не проверяет.
|