Files
airflow-greenplum/docs/bookings_to_gp_dds.md
T
ddadmin 35144e49f6 docs: согласованы руководства загрузок и учебный маршрут
Руководства STG/ODS/DDS/DM сверены с действующими SQL и DAG,
ожидания учебных заглушек связаны с ТЗ. Самопроверка заданий
отделена от проверок разработки и исторических планов.

Для dim_routes закреплено обновление описательных полей только
у открытых версий. SQL, DAG, DDL и настройки стенда не менялись.

Проверки: make lint; make test (4 passed, 14 skipped); карта;
292 локальные ссылки; маршрут чтения и карта в Chromium.
Живой ETL не запускался.
2026-09-20 15:53:39 -04:00

145 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Загрузка 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;` этих свойств не проверяет.