docs: связаны карта DWH, разбор SQL и задания
This commit is contained in:
1 parent
b6e950f52c
commit
b7ecfb1bb7
8 files changed
+342
-149
No files matched your search
@@ -103,8 +103,9 @@ SELECT COUNT(*) FROM dm.sales_report;
|
|||||||
оно проведет от билетов и сегментов к факту и витрине. Рядом держите
|
оно проведет от билетов и сегментов к факту и витрине. Рядом держите
|
||||||
[интерактивную карту](docs/design/architecture-map.html) — локальный файл
|
[интерактивную карту](docs/design/architecture-map.html) — локальный файл
|
||||||
`docs/design/architecture-map.html` нужно открыть в браузере; в Gitea HTML
|
`docs/design/architecture-map.html` нужно открыть в браузере; в Gitea HTML
|
||||||
показывается как исходный текст. Затем переходите к
|
показывается как исходный текст. Затем разберите
|
||||||
[учебным заданиям](docs/assignment/README.md).
|
[готовую загрузку справочника](docs/design/reading_the_pipeline.md#ods-airports)
|
||||||
|
и переходите к [первому заданию ODS](docs/assignment/README.md#первая-загрузка-ods).
|
||||||
|
|
||||||
## DAG-и в стенде
|
## DAG-и в стенде
|
||||||
|
|
||||||
@@ -154,10 +155,10 @@ make clean # полный reset: удалить контейнер
|
|||||||
|
|
||||||
## Документация
|
## Документация
|
||||||
|
|
||||||
- [Учебные задания](docs/assignment/README.md)
|
|
||||||
- [Структура DWH: от бронирования до витрины](docs/design/db_schema.md)
|
- [Структура DWH: от бронирования до витрины](docs/design/db_schema.md)
|
||||||
|
- [От карты к SQL загрузки](docs/design/reading_the_pipeline.md)
|
||||||
|
- [Учебные задания: порядок работы и проверка](docs/assignment/README.md)
|
||||||
- [Интерактивная карта таблиц и связей](docs/design/architecture-map.html)
|
- [Интерактивная карта таблиц и связей](docs/design/architecture-map.html)
|
||||||
- [Как читать пайплайн по скрипту загрузки](docs/design/reading_the_pipeline.md)
|
|
||||||
- [План тестирования/проверок и негативные кейсы](TESTING.md)
|
- [План тестирования/проверок и негативные кейсы](TESTING.md)
|
||||||
- [Дополнительные заметки и технические детали](docs/README.md)
|
- [Дополнительные заметки и технические детали](docs/README.md)
|
||||||
- [Детали по STG DAG](docs/bookings_to_gp_stage.md)
|
- [Детали по STG DAG](docs/bookings_to_gp_stage.md)
|
||||||
|
|||||||
+20
-13
@@ -1,29 +1,36 @@
|
|||||||
# Документация
|
# Документация
|
||||||
|
|
||||||
Этот каталог содержит дополнительные материалы к учебному стенду.
|
## Знакомство и первое задание
|
||||||
|
|
||||||
## Быстрый путь (для менти)
|
После [запуска стенда](../README.md) пройдите три шага:
|
||||||
|
|
||||||
- [Быстрый старт и команды](../README.md)
|
1. [Структура DWH](design/db_schema.md): смысл строк и связей на примере поездки.
|
||||||
- [Структура DWH: от бронирования до витрины](design/db_schema.md)
|
2. [От карты к SQL загрузки](design/reading_the_pipeline.md#ods-airports):
|
||||||
- [Интерактивная карта таблиц и связей](design/architecture-map.html)
|
разбор готового справочника ODS. Сборка факта понадобится позже, перед DDS.
|
||||||
- [Учебные задания](assignment/README.md)
|
3. [Учебные задания](assignment/README.md): файлы для работы, порядок запуска
|
||||||
- [Как читать пайплайн по скрипту загрузки](design/reading_the_pipeline.md)
|
и проверка результата. Поля и бизнес-правила находятся в [ТЗ](assignment/analyst_spec.md).
|
||||||
- [План тестирования и проверки](../TESTING.md)
|
|
||||||
- [Главный учебный DAG: bookings → stg](bookings_to_gp_stage.md)
|
## Найти деталь во время работы
|
||||||
- [Учебный DAG: stg -> ods](bookings_to_gp_ods.md)
|
|
||||||
- [Учебный DAG: ods -> dds](bookings_to_gp_dds.md)
|
[Интерактивная карта](design/architecture-map.html) ведет от таблицы к ее
|
||||||
- [Учебный DAG: dds -> dm](bookings_to_gp_dm.md)
|
источникам, потребителям, SQL и пункту задания.
|
||||||
|
|
||||||
Карту открывайте из локальной копии: `docs/design/architecture-map.html` в браузере.
|
Карту открывайте из локальной копии: `docs/design/architecture-map.html` в браузере.
|
||||||
Сервер не нужен. Просмотр исходного HTML в Gitea не показывает карту.
|
Сервер не нужен. Просмотр исходного HTML в Gitea не показывает карту.
|
||||||
[Руководство](design/db_schema.md#как-открыть-карту) объясняет открытие и адресные ссылки.
|
[Руководство](design/db_schema.md#как-открыть-карту) объясняет открытие и адресные ссылки.
|
||||||
|
|
||||||
|
Если вопрос о запуске или проверке, откройте нужный материал:
|
||||||
|
|
||||||
|
- [Порядок запуска DAG](dag_execution_order.md) и [план проверки стенда](../TESTING.md).
|
||||||
|
- Руководства загрузок: [STG](bookings_to_gp_stage.md), [ODS](bookings_to_gp_ods.md),
|
||||||
|
[DDS](bookings_to_gp_dds.md), [DM](bookings_to_gp_dm.md).
|
||||||
|
- [Соглашения об именах](design/naming_conventions.md) и [классы DQ-проверок](reference/dq_taxonomy.md).
|
||||||
|
|
||||||
## Дизайн (`design/`)
|
## Дизайн (`design/`)
|
||||||
|
|
||||||
- [Единые конвенции нейминга DWH (служебные поля и SCD)](design/naming_conventions.md)
|
- [Единые конвенции нейминга DWH (служебные поля и SCD)](design/naming_conventions.md)
|
||||||
- [Структура DWH и маршрут по карте](design/db_schema.md)
|
- [Структура DWH и маршрут по карте](design/db_schema.md)
|
||||||
- [Как читать пайплайн по скрипту загрузки](design/reading_the_pipeline.md)
|
- [От карты к SQL загрузки](design/reading_the_pipeline.md)
|
||||||
- [Дизайн-документ STG](design/bookings_stg_design.md)
|
- [Дизайн-документ STG](design/bookings_stg_design.md)
|
||||||
- [Дизайн-документ ODS](design/bookings_ods_design.md)
|
- [Дизайн-документ ODS](design/bookings_ods_design.md)
|
||||||
- [Дизайн-документ DDS](design/bookings_dds_design.md)
|
- [Дизайн-документ DDS](design/bookings_dds_design.md)
|
||||||
|
|||||||
+69
-60
@@ -1,85 +1,94 @@
|
|||||||
# Учебные задания
|
# Учебные задания
|
||||||
|
|
||||||
## О чём задание
|
В стенде уже работает цепочка от Bookings до `dm.sales_report`.
|
||||||
|
Вы дополните ее загрузками ODS, измерениями DDS и четырьмя витринами DM.
|
||||||
|
Поля, преобразования и бизнес-правила описаны в [ТЗ](analyst_spec.md).
|
||||||
|
|
||||||
В стенде уже работает **эталонный вертикальный срез** — полная цепочка
|
## Подготовка
|
||||||
`STG → ODS → DDS → DM` для витрины `dm.sales_report`. Ваша задача —
|
|
||||||
реализовать недостающие загрузки по аналогии с эталоном.
|
|
||||||
|
|
||||||
На месте ваших заданий сейчас стоят **заглушки** (`SELECT 1;`). Вы замените
|
Пройдите шаги 1–6 [быстрого старта](../../README.md#быстрый-старт-основной-сценарий-bookings--stg--ods--dds--dm):
|
||||||
их на реальную SQL-логику, и после этого весь DWH будет заполнен.
|
поднимите стенд, создайте таблицы, запустите эталонную цепочку и проверьте данные.
|
||||||
|
Затем прочитайте [руководство о структуре DWH](../design/db_schema.md).
|
||||||
|
Открывайте [карту](../design/architecture-map.html) рядом с заданием;
|
||||||
|
для Gitea есть [инструкция открытия локального HTML](../design/db_schema.md#как-открыть-карту).
|
||||||
|
|
||||||
## С чего начать чтение
|
Таблицы уже созданы, задачи Airflow подключены к SQL-файлам.
|
||||||
|
В учебных `*_load.sql` и `*_dq.sql` пока стоит `SELECT 1;`.
|
||||||
|
Вы заменяете эти заглушки; менять DAG и создавать таблицы заново не требуется.
|
||||||
|
|
||||||
Сначала посмотрите [руководство о структуре DWH и карту](../design/db_schema.md).
|
## Первая загрузка: ODS
|
||||||
Пока пишете свой слой, держите рядом [метод чтения скрипта загрузки](../design/reading_the_pipeline.md).
|
|
||||||
Для `*_dq.sql` сверяйтесь с [классами DQ-проверок](../reference/dq_taxonomy.md).
|
|
||||||
|
|
||||||
## Перед началом
|
Начните с `ods.airplanes`, затем добавьте `ods.seats`.
|
||||||
|
На карте откройте [модели самолетов](../design/architecture-map.html#node=ods.airplanes)
|
||||||
|
и [места](../design/architecture-map.html#node=ods.seats): источники STG уже заполнены.
|
||||||
|
Позже обе таблицы понадобятся `dds.dim_airplanes` для характеристик модели
|
||||||
|
и расчета ее вместимости.
|
||||||
|
|
||||||
Убедитесь, что стенд запущен и данные загружены —
|
### Готовый пример
|
||||||
пройдите шаги 1–6 из [README (Быстрый старт)](../../README.md#быстрый-старт-основной-сценарий-bookings--stg--ods--dds--dm).
|
|
||||||
|
|
||||||
После этого в Greenplum будут данные в эталонных таблицах (`stg.*`, `ods.bookings`,
|
Пройдите [разбор stg.airports → ods.airports](../design/reading_the_pipeline.md#ods-airports).
|
||||||
`dds.dim_airports`, `dds.fact_flight_sales`, `dm.sales_report` и др.).
|
Это близкий пример полного снимка с выбором батча, преобразованиями и дедупликацией.
|
||||||
|
Для первого задания этого разбора достаточно.
|
||||||
|
|
||||||
## Шаг 1. Изучите эталон
|
### Самостоятельная работа
|
||||||
|
|
||||||
Прежде чем писать код, разберите, как устроены эталонные загрузки.
|
Откройте [часть 1 ТЗ](analyst_spec.md#часть-1-ods-слой-operational-data-store).
|
||||||
По одному файлу на каждый ключевой паттерн:
|
Сверяйте с ней поля и правила каждой таблицы:
|
||||||
|
|
||||||
| Паттерн | Файл для изучения | Что посмотреть |
|
| Объект и требования | Файлы для реализации | DDL для сверки |
|
||||||
|---------|-------------------|----------------|
|
|---|---|---|
|
||||||
| ODS: TRUNCATE + INSERT (snapshot-справочник) | `sql/ods/airports_load.sql` | Фильтрация по `stg_batch_id`, дедупликация через `ROW_NUMBER()` |
|
| [ods.airplanes](analyst_spec.md#11-odsairplanes) | [airplanes_load.sql](../../sql/ods/airplanes_load.sql), [airplanes_dq.sql](../../sql/ods/airplanes_dq.sql) | [airplanes_ddl.sql](../../sql/ods/airplanes_ddl.sql) |
|
||||||
| DDS: SCD1 UPSERT (измерение) | `sql/dds/dim_airports_load.sql` | TEMP TABLE → UPDATE (`IS DISTINCT FROM`) → INSERT |
|
| [ods.seats](analyst_spec.md#12-odsseats) | [seats_load.sql](../../sql/ods/seats_load.sql), [seats_dq.sql](../../sql/ods/seats_dq.sql) | [seats_ddl.sql](../../sql/ods/seats_ddl.sql) |
|
||||||
| DM: витрина с UPSERT | `sql/dm/sales_report_load.sql` | HWM-инкремент, агрегация фактов по измерениям |
|
|
||||||
|
|
||||||
Описания каждого DAG — в `docs/`:
|
Пример проверок: [airports_dq.sql](../../sql/ods/airports_dq.sql).
|
||||||
[STG](../bookings_to_gp_stage.md) ·
|
Учтите разницу ключей: у модели это `airplane_code`, у места -
|
||||||
[ODS](../bookings_to_gp_ods.md) ·
|
`(airplane_code, seat_no)`.
|
||||||
[DDS](../bookings_to_gp_dds.md) ·
|
|
||||||
[DM](../bookings_to_gp_dm.md)
|
|
||||||
|
|
||||||
## Шаг 2. Откройте ТЗ
|
### Запуск и проверка
|
||||||
|
|
||||||
Техническое задание от аналитика: **[analyst_spec.md](analyst_spec.md)**.
|
1. Дождитесь успешного `bookings_to_gp_stage` из подготовки.
|
||||||
|
Если STG уже заполнен, новый запуск для этой работы не нужен.
|
||||||
|
2. Запустите `bookings_to_gp_ods` целиком. Он выберет согласованный снимок
|
||||||
|
в `resolve_stg_batch_id`, затем выполнит
|
||||||
|
`load_ods_airplanes → dq_ods_airplanes → load_ods_seats → dq_ods_seats`.
|
||||||
|
SQL использует XCom этого запуска, поэтому отдельно от выбора батча
|
||||||
|
загрузку запускать не стоит.
|
||||||
|
3. В Greenplum посмотрите строки обеих таблиц: модели, типы числовых полей,
|
||||||
|
ключи мест и метки загрузки. Повторите ODS без нового запуска STG:
|
||||||
|
бизнес-строки и количество ключей должны сохраниться без дублей.
|
||||||
|
4. После реализации обеих таблиц запустите `bookings_validate` и откройте
|
||||||
|
группу `validate_ods`. Она проверит наличие данных, совпадение ключей
|
||||||
|
с выбранным STG-батчем, отсутствие дублей и NULL в ключах,
|
||||||
|
а также общий `_load_id` двух таблиц.
|
||||||
|
|
||||||
Там описаны все таблицы, которые нужно реализовать, с маппингами, бизнес-правилами
|
В `bookings_validate` автоматически запускаются все три группы.
|
||||||
и подсказками. Рекомендуемый порядок (от простого к сложному):
|
Пока готовы только задания ODS, оценивайте `validate_ods`: ошибки DDS и DM
|
||||||
|
на этом этапе ожидаемы. После выполнения всех заданий должны пройти все группы.
|
||||||
|
`*_dq.sql` тоже нужно реализовать: успешный `SELECT 1;` ничего не проверяет.
|
||||||
|
|
||||||
1. ODS: `airplanes`, `seats`
|
## Следующие этапы
|
||||||
2. DDS: `dim_airplanes`, `dim_passengers` (SCD1)
|
|
||||||
3. DDS: `dim_routes` (SCD2 — ключевой вызов курсовой)
|
|
||||||
4. DM: `airport_traffic`, `route_performance`, `monthly_overview`, `passenger_loyalty`
|
|
||||||
|
|
||||||
## Шаг 3. Реализуйте задания
|
| Этап | Требования | Готовый пример рядом |
|
||||||
|
|---|---|---|
|
||||||
|
| DDS: `dim_airplanes`, `dim_passengers` | [Модели](analyst_spec.md#21-ddsdim_airplanes-scd1), [пассажиры](analyst_spec.md#22-ddsdim_passengers-scd1) | [dim_airports_load.sql](../../sql/dds/dim_airports_load.sql) |
|
||||||
|
| DDS: `dim_routes` | [Версии маршрута SCD2](analyst_spec.md#23-ddsdim_routes-scd2) | [Разбор сборки факта](../design/reading_the_pipeline.md#fact-flight-sales) показывает, как используется версия |
|
||||||
|
| DM: `airport_traffic` → `route_performance` → `monthly_overview` → `passenger_loyalty` | [Часть 3 ТЗ](analyst_spec.md#часть-3-dm-слой-data-marts--витрины) | [sales_report_load.sql](../../sql/dm/sales_report_load.sql) |
|
||||||
|
|
||||||
- **Файлы-заглушки уже на месте** — например, `sql/ods/airplanes_load.sql`
|
Перед DDS разберите [загрузку факта](../design/reading_the_pipeline.md#fact-flight-sales).
|
||||||
содержит `SELECT 1;`. Ваша задача: заменить `SELECT 1;` на реальную SQL-логику.
|
После реализации измерений выполните [пересчет факта и витрин](analyst_spec.md#пересчёт-факта-после-реализации-измерений).
|
||||||
- **DDL уже создан** — таблицы существуют, создавать их не нужно.
|
Проверяйте готовый слой своей группой в `bookings_validate`.
|
||||||
- **Таски в DAG-ах уже подключены** — `PostgresOperator` ссылается на ваши SQL-файлы.
|
В DDS есть активный SCD2-тест: он временно меняет данные маршрута,
|
||||||
Менять Python-код DAG-ов не нужно.
|
запускает вашу загрузку и затем восстанавливает тестируемые данные.
|
||||||
- **Нейминг полей:** сверяйтесь с `docs/design/naming_conventions.md`.
|
|
||||||
|
|
||||||
## Шаг 4. Проверьте себя валидационным DAG-ом
|
|
||||||
|
|
||||||
DAG **`bookings_validate`** — автоматическая проверка вашей реализации.
|
|
||||||
|
|
||||||
- Три группы проверок: `validate_ods`, `validate_dds`, `validate_dm` — запускаются
|
|
||||||
параллельно. Можно проверять ODS, пока DDS ещё не готов.
|
|
||||||
- Включает активный SCD2-тест для `dim_routes`: загружает изменённые данные
|
|
||||||
и проверяет, что версионирование работает корректно.
|
|
||||||
- Сообщения об ошибках подскажут, что именно не так и что делать дальше.
|
|
||||||
|
|
||||||
**Цель: все проверки зелёные.**
|
|
||||||
|
|
||||||
## Если застряли
|
## Если застряли
|
||||||
|
|
||||||
- Изучите эталонные SQL-скрипты — в них те же паттерны, что нужны для заданий.
|
Вернитесь к карточке объекта на карте: там есть DDL, загрузка, DQ и пункт ТЗ.
|
||||||
- Загляните в `docs/design/naming_conventions.md` — единый источник нейминга полей.
|
Для конкретного вопроса используйте [соглашения об именах](../design/naming_conventions.md),
|
||||||
- Сверьтесь с **веткой `solution`** — там полная рабочая реализация.
|
[справочник DQ](../reference/dq_taxonomy.md) и руководства DAG:
|
||||||
|
[ODS](../bookings_to_gp_ods.md), [DDS](../bookings_to_gp_dds.md), [DM](../bookings_to_gp_dm.md).
|
||||||
|
Полную реализацию можно посмотреть в ветке `solution`.
|
||||||
|
|
||||||
## Для менторов
|
## Для менторов
|
||||||
|
|
||||||
Дизайн заданий и педагогическая логика — в ветке `solution`
|
Дизайн заданий и педагогическая логика находятся в ветке `solution`,
|
||||||
(`docs/design/assignment_design.md`).
|
в `docs/design/assignment_design.md`.
|
||||||
@@ -13,6 +13,11 @@
|
|||||||
>
|
>
|
||||||
> SQL-скрипты эталона лежат в `sql/stg/`, `sql/ods/`, `sql/dds/`, `sql/dm/`.
|
> SQL-скрипты эталона лежат в `sql/stg/`, `sql/ods/`, `sql/dds/`, `sql/dm/`.
|
||||||
|
|
||||||
|
Порядок запуска и проверки первой загрузки находится во
|
||||||
|
[входе в задания](README.md#первая-загрузка-ods). В каждом разделе ниже есть
|
||||||
|
ссылка на объект карты и близкий готовый пример. Карту открывайте
|
||||||
|
[локально в браузере](../design/db_schema.md#как-открыть-карту).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Рекомендуемый порядок выполнения
|
## Рекомендуемый порядок выполнения
|
||||||
@@ -36,7 +41,7 @@
|
|||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
- **Нейминг полей:** см. `docs/design/naming_conventions.md` — единый источник
|
- **Нейминг полей:** см. [соглашения об именах](../design/naming_conventions.md) — единый источник
|
||||||
истины для служебных полей (`_load_id`, `_load_ts`, `created_at`, `updated_at`,
|
истины для служебных полей (`_load_id`, `_load_ts`, `created_at`, `updated_at`,
|
||||||
`valid_from`, `valid_to`, `hashdiff`, суффиксы `_bk` / `_sk`).
|
`valid_from`, `valid_to`, `hashdiff`, суффиксы `_bk` / `_sk`).
|
||||||
- **SQL-файлы:** располагайте в `sql/{слой}/{объект}_{роль}.sql`
|
- **SQL-файлы:** располагайте в `sql/{слой}/{объект}_{роль}.sql`
|
||||||
@@ -46,7 +51,10 @@
|
|||||||
Вам нужно только заменить содержимое SQL-файлов — DAG менять не нужно.
|
Вам нужно только заменить содержимое SQL-файлов — DAG менять не нужно.
|
||||||
- **Идемпотентность:** каждый скрипт загрузки должен быть безопасен
|
- **Идемпотентность:** каждый скрипт загрузки должен быть безопасен
|
||||||
при повторном запуске (не создавать дубликатов).
|
при повторном запуске (не создавать дубликатов).
|
||||||
- **Шаблон `{{ run_id }}`:** используйте Jinja-шаблон Airflow для `_load_id`.
|
- **Метки загрузки:** в ODS-снимках `airplanes` и `seats` сохраняйте в `_load_id`
|
||||||
|
идентификатор исходного STG-батча из `resolve_stg_batch_id`, как в `ods.airports`.
|
||||||
|
Так проверка сможет сопоставить ODS с тем снимком, из которого он загружен.
|
||||||
|
В заданиях DDS и DM используйте `'{{ run_id }}'` текущего запуска слоя.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -67,11 +75,11 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
`ods.routes` тоже эталонный: факт `fact_flight_sales` использует его для резолвинга
|
`ods.routes` тоже эталонный: факт `fact_flight_sales` использует его для резолвинга
|
||||||
аэропортов вылета/прилёта, независимо от студенческого `dds.dim_routes`.
|
аэропортов вылета/прилёта, независимо от студенческого `dds.dim_routes`.
|
||||||
|
|
||||||
**Что вам делать:** Изучите эталонные скрипты как образец — именно так написан
|
Для первого задания разберите [загрузку `ods.airports`](../design/reading_the_pipeline.md#ods-airports).
|
||||||
«боевой» код загрузки:
|
При необходимости проследить получение снимка откройте
|
||||||
- `sql/stg/airports_load.sql` — инкрементальная загрузка (HWM)
|
[sql/stg/airports_load.sql](../../sql/stg/airports_load.sql) или
|
||||||
- `sql/stg/airplanes_load.sql` — full snapshot с проверкой `_load_id`
|
[sql/stg/airplanes_load.sql](../../sql/stg/airplanes_load.sql): оба читают полный
|
||||||
- `sql/ods/airports_load.sql` — TRUNCATE+INSERT из STG
|
снимок источника. `NOT EXISTS` защищает от повторной вставки ключа в том же `_load_id`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,10 +89,14 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
> Справочники (`airplanes`, `seats`) загружаются стратегией TRUNCATE + INSERT
|
> Справочники (`airplanes`, `seats`) загружаются стратегией TRUNCATE + INSERT
|
||||||
> из последнего согласованного батча STG. (`ods.routes` реализован в эталоне.)
|
> из последнего согласованного батча STG. (`ods.routes` реализован в эталоне.)
|
||||||
>
|
>
|
||||||
> **Аналог для изучения:** `sql/ods/airports_ddl.sql`, `sql/ods/airports_load.sql`
|
> **Аналог для изучения:** [DDL airports](../../sql/ods/airports_ddl.sql),
|
||||||
|
> [загрузка airports](../../sql/ods/airports_load.sql) и [ее разбор](../design/reading_the_pipeline.md#ods-airports).
|
||||||
|
|
||||||
### 1.1. ods.airplanes
|
### 1.1. ods.airplanes
|
||||||
|
|
||||||
|
[На карте: модель самолета и ее источник](../design/architecture-map.html#node=ods.airplanes).
|
||||||
|
Готовый пример полного снимка: [ods.airports](../design/reading_the_pipeline.md#ods-airports).
|
||||||
|
|
||||||
**Описание:** Очищенный справочник моделей воздушных судов с правильными типами.
|
**Описание:** Очищенный справочник моделей воздушных судов с правильными типами.
|
||||||
|
|
||||||
**Источник:** `stg.airplanes`
|
**Источник:** `stg.airplanes`
|
||||||
@@ -95,7 +107,7 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
| `model` | TEXT NOT NULL | Наименование модели | `model` (парсинг JSON: `model::JSON->>'ru'` — если JSON, иначе `model` как есть) |
|
| `model` | TEXT NOT NULL | Наименование модели | `model` (парсинг JSON: `model::JSON->>'ru'` — если JSON, иначе `model` как есть) |
|
||||||
| `range_km` | INTEGER | Дальность полёта, км | `range::INTEGER` |
|
| `range_km` | INTEGER | Дальность полёта, км | `range::INTEGER` |
|
||||||
| `speed_kmh` | INTEGER | Крейсерская скорость, км/ч | `speed::INTEGER` |
|
| `speed_kmh` | INTEGER | Крейсерская скорость, км/ч | `speed::INTEGER` |
|
||||||
| `_load_id` | TEXT NOT NULL | Идентификатор батча | `'{{ run_id }}'` |
|
| `_load_id` | TEXT NOT NULL | Идентификатор исходного STG-батча | `'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'` |
|
||||||
| `_load_ts` | TIMESTAMP NOT NULL | Момент загрузки | `now()` |
|
| `_load_ts` | TIMESTAMP NOT NULL | Момент загрузки | `now()` |
|
||||||
|
|
||||||
**Тип историзации:** Нет (текущее состояние справочника, TRUNCATE + INSERT).
|
**Тип историзации:** Нет (текущее состояние справочника, TRUNCATE + INSERT).
|
||||||
@@ -115,6 +127,9 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
|
|
||||||
### 1.2. ods.seats
|
### 1.2. ods.seats
|
||||||
|
|
||||||
|
[На карте: места и связь с моделью](../design/architecture-map.html#node=ods.seats).
|
||||||
|
Пример выбора снимка и дедупликации: [ods.airports](../design/reading_the_pipeline.md#ods-airports).
|
||||||
|
|
||||||
**Описание:** Карта посадочных мест с корректными типами.
|
**Описание:** Карта посадочных мест с корректными типами.
|
||||||
|
|
||||||
**Источник:** `stg.seats`
|
**Источник:** `stg.seats`
|
||||||
@@ -124,7 +139,7 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
| `airplane_code` | TEXT NOT NULL | Код модели (PK, часть 1) | `airplane_code` |
|
| `airplane_code` | TEXT NOT NULL | Код модели (PK, часть 1) | `airplane_code` |
|
||||||
| `seat_no` | TEXT NOT NULL | Номер места (PK, часть 2) | `seat_no` |
|
| `seat_no` | TEXT NOT NULL | Номер места (PK, часть 2) | `seat_no` |
|
||||||
| `fare_conditions` | TEXT NOT NULL | Класс обслуживания | `fare_conditions` |
|
| `fare_conditions` | TEXT NOT NULL | Класс обслуживания | `fare_conditions` |
|
||||||
| `_load_id` | TEXT NOT NULL | Идентификатор батча | `'{{ run_id }}'` |
|
| `_load_id` | TEXT NOT NULL | Идентификатор исходного STG-батча | `'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'` |
|
||||||
| `_load_ts` | TIMESTAMP NOT NULL | Момент загрузки | `now()` |
|
| `_load_ts` | TIMESTAMP NOT NULL | Момент загрузки | `now()` |
|
||||||
|
|
||||||
**Тип историзации:** Нет (текущее состояние справочника, TRUNCATE + INSERT).
|
**Тип историзации:** Нет (текущее состояние справочника, TRUNCATE + INSERT).
|
||||||
@@ -149,10 +164,14 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
> ключами. SCD1-измерения обновляют атрибуты «на месте». SCD2-измерение
|
> ключами. SCD1-измерения обновляют атрибуты «на месте». SCD2-измерение
|
||||||
> хранит историю изменений через версионирование.
|
> хранит историю изменений через версионирование.
|
||||||
>
|
>
|
||||||
> **Аналог для SCD1:** `sql/dds/dim_airports_ddl.sql`, `sql/dds/dim_airports_load.sql`
|
> **Аналог для SCD1:** [DDL dim_airports](../../sql/dds/dim_airports_ddl.sql),
|
||||||
|
> [загрузка dim_airports](../../sql/dds/dim_airports_load.sql).
|
||||||
|
|
||||||
### 2.1. dds.dim_airplanes (SCD1)
|
### 2.1. dds.dim_airplanes (SCD1)
|
||||||
|
|
||||||
|
[На карте: характеристики и вместимость модели](../design/architecture-map.html#node=dds.dim_airplanes).
|
||||||
|
Пример SCD1: [dim_airports_load.sql](../../sql/dds/dim_airports_load.sql).
|
||||||
|
|
||||||
**Описание:** Измерение моделей самолётов. Содержит технические характеристики
|
**Описание:** Измерение моделей самолётов. Содержит технические характеристики
|
||||||
и рассчитанное общее количество мест (обогащение из `ods.seats`).
|
и рассчитанное общее количество мест (обогащение из `ods.seats`).
|
||||||
|
|
||||||
@@ -184,7 +203,8 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
в `ods.seats` для данного `airplane_code` (LEFT JOIN).
|
в `ods.seats` для данного `airplane_code` (LEFT JOIN).
|
||||||
|
|
||||||
**Генерация суррогатного ключа:** `MAX(airplane_sk) + ROW_NUMBER()`.
|
**Генерация суррогатного ключа:** `MAX(airplane_sk) + ROW_NUMBER()`.
|
||||||
Безопасно при `concurrency=1` в DAG.
|
Загрузки одной таблицы должны выполняться последовательно. В DDS DAG
|
||||||
|
параллельные запуски ограничены параметром `max_active_runs=1`.
|
||||||
|
|
||||||
**Distribution Key:** `airplane_sk`
|
**Distribution Key:** `airplane_sk`
|
||||||
|
|
||||||
@@ -192,6 +212,9 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
|
|
||||||
### 2.2. dds.dim_passengers (SCD1)
|
### 2.2. dds.dim_passengers (SCD1)
|
||||||
|
|
||||||
|
[На карте: пассажир из билетов](../design/architecture-map.html#node=dds.dim_passengers).
|
||||||
|
Пример обновления атрибутов и вставки новых ключей: [dim_airports_load.sql](../../sql/dds/dim_airports_load.sql).
|
||||||
|
|
||||||
**Описание:** Измерение пассажиров. Извлекается из таблицы билетов — каждый
|
**Описание:** Измерение пассажиров. Извлекается из таблицы билетов — каждый
|
||||||
уникальный `passenger_id` становится строкой измерения.
|
уникальный `passenger_id` становится строкой измерения.
|
||||||
|
|
||||||
@@ -230,6 +253,10 @@ STG-слой (все 9 таблиц) и `ods.routes` полностью реал
|
|||||||
|
|
||||||
### 2.3. dds.dim_routes (SCD2)
|
### 2.3. dds.dim_routes (SCD2)
|
||||||
|
|
||||||
|
[На карте: версии маршрута](../design/architecture-map.html#node=dds.dim_routes).
|
||||||
|
В [разборе готового факта](../design/reading_the_pipeline.md#fact-joins) видно,
|
||||||
|
как загрузчик выбирает версию на дату рейса. Алгоритм самой SCD2-загрузки дан ниже.
|
||||||
|
|
||||||
> **Это ключевой вызов курсовой.** Реализация SCD Type 2 — обязательный навык
|
> **Это ключевой вызов курсовой.** Реализация SCD Type 2 — обязательный навык
|
||||||
> для Data Engineer. Ниже — алгоритм текстом; SQL вы пишете самостоятельно.
|
> для Data Engineer. Ниже — алгоритм текстом; SQL вы пишете самостоятельно.
|
||||||
> Если застряли — сверьтесь с веткой `solution`.
|
> Если застряли — сверьтесь с веткой `solution`.
|
||||||
@@ -317,10 +344,15 @@ md5(concat_ws('|',
|
|||||||
> **Цель:** Построить аналитические витрины поверх DDS.
|
> **Цель:** Построить аналитические витрины поверх DDS.
|
||||||
> Каждая витрина отвечает на конкретный бизнес-вопрос.
|
> Каждая витрина отвечает на конкретный бизнес-вопрос.
|
||||||
>
|
>
|
||||||
> **Аналог для изучения:** `sql/dm/sales_report_ddl.sql`, `sql/dm/sales_report_load.sql`
|
> **Аналог для изучения:** [DDL sales_report](../../sql/dm/sales_report_ddl.sql),
|
||||||
|
> [загрузка sales_report](../../sql/dm/sales_report_load.sql).
|
||||||
|
|
||||||
### 3.1. dm.airport_traffic
|
### 3.1. dm.airport_traffic
|
||||||
|
|
||||||
|
[На карте: аэропорт за день](../design/architecture-map.html#node=dm.airport_traffic).
|
||||||
|
Пример пересчета затронутых дат: [sales_report_load.sql](../../sql/dm/sales_report_load.sql).
|
||||||
|
Две роли аэропорта проследите в [готовом факте](../design/reading_the_pipeline.md#fact-joins).
|
||||||
|
|
||||||
**Бизнес-вопрос:** «Какой пассажиропоток и выручка у каждого аэропорта по дням?»
|
**Бизнес-вопрос:** «Какой пассажиропоток и выручка у каждого аэропорта по дням?»
|
||||||
|
|
||||||
**Описание:** Ежедневная статистика по каждому аэропорту: сколько рейсов
|
**Описание:** Ежедневная статистика по каждому аэропорту: сколько рейсов
|
||||||
@@ -359,6 +391,11 @@ md5(concat_ws('|',
|
|||||||
и `arrival_airport_sk`. Затем сгруппируйте по `(traffic_date, airport_sk)`.
|
и `arrival_airport_sk`. Затем сгруппируйте по `(traffic_date, airport_sk)`.
|
||||||
|
|
||||||
**Метрики:**
|
**Метрики:**
|
||||||
|
|
||||||
|
Здесь `SUM(is_boarded)` означает число строк с признаком посадки.
|
||||||
|
Поле булево; в SQL используйте `SUM(CASE WHEN is_boarded THEN 1 ELSE 0 END)`,
|
||||||
|
как в `sales_report_load.sql`.
|
||||||
|
|
||||||
- `departures_flights` — `COUNT(DISTINCT flight_id)` для роли «вылет»
|
- `departures_flights` — `COUNT(DISTINCT flight_id)` для роли «вылет»
|
||||||
- `departures_passengers` — `SUM(is_boarded)` для роли «вылет»
|
- `departures_passengers` — `SUM(is_boarded)` для роли «вылет»
|
||||||
- `departures_revenue` — `SUM(price)` для роли «вылет»
|
- `departures_revenue` — `SUM(price)` для роли «вылет»
|
||||||
@@ -377,6 +414,10 @@ md5(concat_ws('|',
|
|||||||
|
|
||||||
### 3.2. dm.route_performance
|
### 3.2. dm.route_performance
|
||||||
|
|
||||||
|
[На карте: статистика маршрута](../design/architecture-map.html#node=dm.route_performance).
|
||||||
|
Соединение факта с измерениями и агрегация показаны в
|
||||||
|
[sales_report_load.sql](../../sql/dm/sales_report_load.sql); здесь по ТЗ нужен полный пересчет.
|
||||||
|
|
||||||
**Бизнес-вопрос:** «Какие маршруты самые эффективные? Где высокий load factor,
|
**Бизнес-вопрос:** «Какие маршруты самые эффективные? Где высокий load factor,
|
||||||
а где теряем пассажиров?»
|
а где теряем пассажиров?»
|
||||||
|
|
||||||
@@ -427,9 +468,8 @@ md5(concat_ws('|',
|
|||||||
- `avg_boarding_rate` — `AVG(CASE WHEN is_boarded THEN 1 ELSE 0 END)`
|
- `avg_boarding_rate` — `AVG(CASE WHEN is_boarded THEN 1 ELSE 0 END)`
|
||||||
- `avg_load_factor` — `total_boarded / (total_flights * total_seats)`
|
- `avg_load_factor` — `total_boarded / (total_flights * total_seats)`
|
||||||
|
|
||||||
**Тип хранения Greenplum:** AO Column Store (zstd). Идеален для аналитики:
|
**Тип хранения Greenplum:** AO Column Store (zstd), сжатое колоночное хранение.
|
||||||
отличное сжатие, чтение только нужных колонок. AO не поддерживает UPDATE —
|
В этом задании небольшой результат полностью пересоздается при каждом запуске.
|
||||||
поэтому используем Full Rebuild.
|
|
||||||
|
|
||||||
**Distribution Key:** `route_bk`
|
**Distribution Key:** `route_bk`
|
||||||
|
|
||||||
@@ -440,6 +480,10 @@ md5(concat_ws('|',
|
|||||||
|
|
||||||
### 3.3. dm.monthly_overview
|
### 3.3. dm.monthly_overview
|
||||||
|
|
||||||
|
[На карте: месяц и модель самолета](../design/architecture-map.html#node=dm.monthly_overview).
|
||||||
|
Пример отбора затронутых периодов: [sales_report_load.sql](../../sql/dm/sales_report_load.sql).
|
||||||
|
В этом задании период и уровни агрегации определены ниже.
|
||||||
|
|
||||||
**Бизнес-вопрос:** «Какова помесячная динамика: рейсы, выручка, load factor
|
**Бизнес-вопрос:** «Какова помесячная динамика: рейсы, выручка, load factor
|
||||||
в разрезе типов самолётов?»
|
в разрезе типов самолётов?»
|
||||||
|
|
||||||
@@ -479,9 +523,11 @@ md5(concat_ws('|',
|
|||||||
|
|
||||||
**Ключевой приём — Двухуровневая агрегация:**
|
**Ключевой приём — Двухуровневая агрегация:**
|
||||||
|
|
||||||
Чтобы честно посчитать среднюю заполняемость (`avg_load_factor`), нельзя просто
|
В этом задании `avg_load_factor` - среднее долей занятых мест по рейсам:
|
||||||
поделить `SUM(boarded)` на `SUM(seats)` — это даёт ошибку (парадокс Симпсона).
|
каждый рейс имеет одинаковый вес. Отношение `SUM(boarded) / SUM(seats)`
|
||||||
Правильный путь:
|
дает среднее с весом по вместимости и при разной вместимости может отличаться.
|
||||||
|
Нужный расчет:
|
||||||
|
|
||||||
1. **Уровень 1 (рейс):** Для каждого `flight_id` посчитайте `load_factor = boarded / total_seats`.
|
1. **Уровень 1 (рейс):** Для каждого `flight_id` посчитайте `load_factor = boarded / total_seats`.
|
||||||
2. **Уровень 2 (месяц):** Возьмите `AVG(load_factor)` по всем рейсам месяца.
|
2. **Уровень 2 (месяц):** Возьмите `AVG(load_factor)` по всем рейсам месяца.
|
||||||
|
|
||||||
@@ -502,6 +548,10 @@ md5(concat_ws('|',
|
|||||||
|
|
||||||
### 3.4. dm.passenger_loyalty
|
### 3.4. dm.passenger_loyalty
|
||||||
|
|
||||||
|
[На карте: история пассажира](../design/architecture-map.html#node=dm.passenger_loyalty).
|
||||||
|
В [sales_report_load.sql](../../sql/dm/sales_report_load.sql) посмотрите отделение
|
||||||
|
затронутых дат от полной истории за эти даты. Здесь единица пересчета - пассажир.
|
||||||
|
|
||||||
**Бизнес-вопрос:** «Кто наши самые лояльные пассажиры? Сколько они летают,
|
**Бизнес-вопрос:** «Кто наши самые лояльные пассажиры? Сколько они летают,
|
||||||
тратят, какой класс предпочитают?»
|
тратят, какой класс предпочитают?»
|
||||||
|
|
||||||
@@ -568,6 +618,10 @@ md5(concat_ws('|',
|
|||||||
|
|
||||||
## Пересчёт факта после реализации измерений
|
## Пересчёт факта после реализации измерений
|
||||||
|
|
||||||
|
[Факт на карте](../design/architecture-map.html#node=dds.fact_flight_sales) и
|
||||||
|
[разбор его INSERT и UPDATE](../design/reading_the_pipeline.md#найдите-запись-результата)
|
||||||
|
помогут проследить, когда заполняются ключи измерений.
|
||||||
|
|
||||||
После того, как вы реализуете все DDS-измерения (`dim_airplanes`, `dim_passengers`,
|
После того, как вы реализуете все DDS-измерения (`dim_airplanes`, `dim_passengers`,
|
||||||
`dim_routes`), нужно пересчитать факт — он загружался ещё без ваших SK:
|
`dim_routes`), нужно пересчитать факт — он загружался ещё без ваших SK:
|
||||||
|
|
||||||
|
|||||||
@@ -1268,6 +1268,10 @@
|
|||||||
{
|
{
|
||||||
"label": "DQ",
|
"label": "DQ",
|
||||||
"url": "../../sql/ods/airports_dq.sql"
|
"url": "../../sql/ods/airports_dq.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#ods-airports"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parts": []
|
"parts": []
|
||||||
@@ -1836,6 +1840,10 @@
|
|||||||
{
|
{
|
||||||
"label": "Пересчет после реализации измерений",
|
"label": "Пересчет после реализации измерений",
|
||||||
"url": "../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений"
|
"url": "../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-flight-sales"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parts": []
|
"parts": []
|
||||||
@@ -2633,6 +2641,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/ods/airports_load.sql"
|
"url": "../../sql/ods/airports_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#ods-airports"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3356,6 +3368,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3397,6 +3413,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3469,6 +3489,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3505,6 +3529,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1068,6 +1068,10 @@
|
|||||||
{
|
{
|
||||||
"label": "DQ",
|
"label": "DQ",
|
||||||
"url": "../../sql/ods/airports_dq.sql"
|
"url": "../../sql/ods/airports_dq.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#ods-airports"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parts": []
|
"parts": []
|
||||||
@@ -1636,6 +1640,10 @@
|
|||||||
{
|
{
|
||||||
"label": "Пересчет после реализации измерений",
|
"label": "Пересчет после реализации измерений",
|
||||||
"url": "../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений"
|
"url": "../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-flight-sales"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parts": []
|
"parts": []
|
||||||
@@ -2433,6 +2441,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/ods/airports_load.sql"
|
"url": "../../sql/ods/airports_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#ods-airports"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3156,6 +3168,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3197,6 +3213,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3269,6 +3289,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3305,6 +3329,10 @@
|
|||||||
{
|
{
|
||||||
"label": "SQL",
|
"label": "SQL",
|
||||||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Разбор SQL загрузки",
|
||||||
|
"url": "reading_the_pipeline.md#fact-joins"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ SQL использует `LEFT JOIN`, поэтому строка с таким
|
|||||||
## Куда идти дальше
|
## Куда идти дальше
|
||||||
|
|
||||||
Для первого задания разберите готовую загрузку справочника:
|
Для первого задания разберите готовую загрузку справочника:
|
||||||
[как читать SQL загрузки](reading_the_pipeline.md), затем откройте
|
[от карты к SQL загрузки](reading_the_pipeline.md#ods-airports), затем откройте
|
||||||
[учебные задания](../assignment/README.md). Для точечного вопроса
|
[первое задание ODS](../assignment/README.md#первая-загрузка-ods). Для точечного вопроса
|
||||||
о таблице возвращайтесь на карту: там находится каталог объектов,
|
о таблице возвращайтесь на карту: там находится каталог объектов,
|
||||||
их источников, потребителей, SQL и DQ.
|
их источников, потребителей, SQL и DQ.
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +1,132 @@
|
|||||||
# Как читать пайплайн по скрипту загрузки
|
# От карты к SQL загрузки
|
||||||
|
|
||||||
Большую схему не нужно запоминать. Связи любой таблицы уже записаны в блоке
|
На [карте](architecture-map.html) найдите таблицу, которую хотите заполнить,
|
||||||
`FROM ... JOIN ...` её файла `*_load.sql`. Обычно каждая прочитанная таблица
|
и откройте ее SQL загрузки. Проследите, какие строки скрипт читает,
|
||||||
даёт одно входящее ребро. Если одна таблица читается под разными псевдонимами,
|
что меняет и по какому ключу записывает результат.
|
||||||
как `dim_airports`, её можно показать одним узлом.
|
Смысл строк и связей разобран в [руководстве о структуре DWH](db_schema.md).
|
||||||
|
|
||||||
## Метод
|
Для первого задания достаточно примера `ods.airports` ниже.
|
||||||
|
К сборке факта вернитесь перед заданиями DDS.
|
||||||
|
Адресные ссылки на карту работают в локальном просмотре;
|
||||||
|
если читаете в Gitea, используйте [инструкцию открытия карты](db_schema.md#как-открыть-карту).
|
||||||
|
|
||||||
`INNER JOIN` показывает обязательную часть основного потока. Если входной строки
|
<a id="ods-airports"></a>
|
||||||
нет, целевая строка не появится. `LEFT JOIN` к измерениям решает две задачи.
|
|
||||||
Факт может прийти раньше измерения: это опаздывающее измерение
|
|
||||||
(late-arriving dimension). Эталонный факт также не должен зависеть от того,
|
|
||||||
успел ли студент реализовать свои измерения. Поэтому строка факта сохраняется,
|
|
||||||
а её суррогатный ключ пока может быть `NULL`.
|
|
||||||
|
|
||||||
В этом CTE есть и другие `LEFT JOIN`. `ods.routes` даёт эталонный путь к
|
## Справочник: stg.airports → ods.airports
|
||||||
аэропортам, а `ods.boarding_passes` содержит необязательные `seat_no` и
|
|
||||||
признак посадки.
|
|
||||||
|
|
||||||
Если в загрузчике несколько SQL-операторов, смотрите на источник `INSERT`.
|
Откройте [ods.airports на карте](architecture-map.html#node=ods.airports)
|
||||||
Именно он задаёт полный список входящих связей для новых строк. Например, в
|
и связь [Типизация и загрузка ODS](architecture-map.html#edge=flow-stg.airports-ods.airports-13).
|
||||||
`fact_flight_sales_load.sql` блок `FROM` у `UPDATE` читает только изменяемые
|
Здесь один источник: `stg.airports`. В STG накапливаются снимки разных запусков,
|
||||||
поля, а полный список находится в CTE `fact_src` перед `INSERT`.
|
а в ODS нужна одна строка на аэропорт из выбранного снимка.
|
||||||
|
|
||||||
## Пример: `fact_flight_sales`
|
Рядом откройте [airports_load.sql](../../sql/ods/airports_load.sql).
|
||||||
|
Пройдите скрипт по порядку:
|
||||||
|
|
||||||
Откройте [`sql/dds/fact_flight_sales_load.sql`](../../sql/dds/fact_flight_sales_load.sql)
|
1. `TRUNCATE TABLE ods.airports` очищает предыдущий снимок.
|
||||||
и пройдите `fact_src` сверху вниз:
|
Следующий `INSERT` заменяет его целиком, поэтому исчезнувший из нового
|
||||||
|
снимка аэропорт исчезнет и из ODS.
|
||||||
|
2. В CTE `src` найдите `FROM stg.airports` и фильтр `_load_id`:
|
||||||
|
|
||||||
1. `FROM ods.segments` задаёт зерно `(ticket_no, flight_id)` и поле `price`.
|
```sql
|
||||||
2. `JOIN ods.tickets` обязателен для `book_ref` и `passenger_id`.
|
WHERE s._load_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text
|
||||||
3. `JOIN ods.bookings` обязателен для `book_date`.
|
```
|
||||||
4. `JOIN ods.flights` обязателен для расписания и `route_no`.
|
|
||||||
5. `LEFT JOIN ods.routes` находит аэропорты по `route_no`. Этот эталонный путь
|
|
||||||
не зависит от студенческого `dim_routes`.
|
|
||||||
6. `LEFT JOIN dds.dim_routes` ищет point-in-time версию маршрута и `route_sk`.
|
|
||||||
7. `LEFT JOIN dds.dim_calendar` даёт `calendar_sk` по дате вылета.
|
|
||||||
8. Два `LEFT JOIN dds.dim_airports` дают ключи аэропортов вылета и прилёта.
|
|
||||||
На схеме их можно показать одним узлом `dim_airports`.
|
|
||||||
9. `LEFT JOIN dds.dim_airplanes` даёт `airplane_sk` через найденную версию маршрута.
|
|
||||||
10. `LEFT JOIN dds.dim_tariffs` даёт `tariff_sk`.
|
|
||||||
11. `LEFT JOIN dds.dim_passengers` даёт `passenger_sk`.
|
|
||||||
12. `LEFT JOIN ods.boarding_passes` даёт `seat_no` и признак `is_boarded`.
|
|
||||||
|
|
||||||
Этот путь разобран в разделе [«Сборка факта продаж»](db_schema.md#сборка-факта-продаж).
|
Задача `resolve_stg_batch_id` в [ODS DAG](../../airflow/dags/bookings_to_gp_ods.py)
|
||||||
На карте сплошные стрелки показывают готовые загрузки, пунктирные — ожидаемые
|
выбирает общий батч четырех справочников STG или берет `stg_batch_id`
|
||||||
по ТЗ. Тип соединения и поведение при отсутствии строки описаны в карточке связи.
|
из параметров запуска. Этот фильтр оставляет один снимок из истории STG.
|
||||||
|
3. Выражения `airport_name::json->>'ru'`, `city::json->>'ru'`
|
||||||
|
и `country::json->>'ru'` извлекают русские названия. Остальные бизнес-поля
|
||||||
|
переносятся без преобразования.
|
||||||
|
4. `ROW_NUMBER()` нумерует строки внутри `airport_code` по `_load_ts DESC`,
|
||||||
|
затем `event_ts DESC NULLS LAST`. Финальный `WHERE s.rn = 1`
|
||||||
|
оставляет одну строку на код.
|
||||||
|
5. Сопоставьте колонки `INSERT INTO ods.airports (...)` с финальным `SELECT`.
|
||||||
|
В `_load_id` сохраняется идентификатор выбранного STG-батча,
|
||||||
|
в `_load_ts` записывается время загрузки ODS. Поля и типы результата
|
||||||
|
заданы в [airports_ddl.sql](../../sql/ods/airports_ddl.sql).
|
||||||
|
6. `ANALYZE` обновляет статистику таблицы для планировщика запросов.
|
||||||
|
Проверки находятся отдельно: [airports_dq.sql](../../sql/ods/airports_dq.sql)
|
||||||
|
сравнивает множества ключей STG-батча и ODS, ищет дубли и пустые обязательные поля.
|
||||||
|
|
||||||
|
Повторная загрузка того же снимка должна сохранить бизнес-строки без дублей;
|
||||||
|
время `_load_ts` при этом обновится.
|
||||||
|
Теперь переходите к [первой самостоятельной загрузке](../assignment/README.md#первая-загрузка-ods).
|
||||||
|
|
||||||
|
<a id="пример-fact_flight_sales"></a>
|
||||||
|
<a id="fact-flight-sales"></a>
|
||||||
|
|
||||||
|
## Факт: сегмент билета и его измерения
|
||||||
|
|
||||||
|
Откройте [dds.fact_flight_sales](architecture-map.html#node=dds.fact_flight_sales)
|
||||||
|
и [fact_flight_sales_load.sql](../../sql/dds/fact_flight_sales_load.sql).
|
||||||
|
Одна строка факта соответствует паре `(ticket_no, flight_id)`.
|
||||||
|
В [примере поездки](db_schema.md#сборка-факта-продаж) два билета с тремя
|
||||||
|
сегментами дают три строки факта.
|
||||||
|
|
||||||
|
<a id="метод"></a>
|
||||||
|
<a id="fact-joins"></a>
|
||||||
|
|
||||||
|
### Найдите источник новых строк
|
||||||
|
|
||||||
|
В файле сначала идет `UPDATE`, а затем CTE `fact_src` и `INSERT`.
|
||||||
|
Начните с `fact_src`: здесь виден полный набор источников новых строк.
|
||||||
|
`FROM ods.segments AS seg` задает зерно, а `seg.amount AS price`
|
||||||
|
переносит цену сегмента в факт.
|
||||||
|
|
||||||
|
| Участок `fact_src` | Что добавляется к сегменту | Связь на карте |
|
||||||
|
|---|---|---|
|
||||||
|
| `JOIN ods.tickets`, затем `JOIN ods.bookings` | Бронирование, пассажир и дата покупки | [Билет и пассажир](architecture-map.html#edge=fact-1), [дата бронирования](architecture-map.html#edge=fact-2) |
|
||||||
|
| `JOIN ods.flights` | Дата вылета и номер маршрута | [Рейс, дата и маршрут](architecture-map.html#edge=fact-3) |
|
||||||
|
| `LEFT JOIN` подзапроса из `ods.routes` | Коды аэропортов из строки с наибольшим `validity` на `route_no` | [Коды двух аэропортов](architecture-map.html#edge=fact-4) |
|
||||||
|
| Два `LEFT JOIN dds.dim_airports` как `dep` и `arr` | `departure_airport_sk` и `arrival_airport_sk` по двум кодам | [Вылет](architecture-map.html#edge=fact-7), [прилет](architecture-map.html#edge=fact-8) |
|
||||||
|
| `LEFT JOIN dds.dim_routes`, затем `dds.dim_airplanes` | Версия маршрута на дату рейса и SK ее модели самолета | [Версия маршрута](architecture-map.html#edge=fact-5), [модель](architecture-map.html#edge=fact-9) |
|
||||||
|
| `LEFT JOIN dds.dim_calendar`, `dds.dim_tariffs`, `dds.dim_passengers` | SK по дате вылета, классу обслуживания и `passenger_id` | [Дата](architecture-map.html#edge=fact-6), [тариф](architecture-map.html#edge=fact-10), [пассажир](architecture-map.html#edge=fact-11) |
|
||||||
|
| `LEFT JOIN ods.boarding_passes` по обоим полям ключа | `seat_no`; наличие талона задает `is_boarded` | [Посадка и место](architecture-map.html#edge=fact-12) |
|
||||||
|
|
||||||
|
Если билет, бронирование или рейс не найдены, `INNER JOIN` исключит сегмент
|
||||||
|
из вставки. `LEFT JOIN` сохранит его при отсутствии совпадения: например,
|
||||||
|
без талона получатся `seat_no = NULL` и `is_boarded = false`.
|
||||||
|
|
||||||
|
Обратите внимание на два разных пути от маршрута:
|
||||||
|
|
||||||
|
- Аэропорты берутся через готовый `ods.routes`. Отбор `ORDER BY validity DESC`
|
||||||
|
работает по полю TEXT и не использует дату рейса.
|
||||||
|
- `route_sk` ищется в студенческом `dim_routes` по дате вылета
|
||||||
|
в интервале `[valid_from, valid_to)`. Из этой версии берется `airplane_code`
|
||||||
|
для поиска `airplane_sk`.
|
||||||
|
|
||||||
|
До выполнения заданий `route_sk`, `airplane_sk` и `passenger_sk` могут быть
|
||||||
|
`NULL`. Путь аэропортов от студенческих измерений не зависит.
|
||||||
|
Объяснение этой части модели: [аэропорты и версия маршрута](db_schema.md#аэропорты-вылета-и-прилета).
|
||||||
|
|
||||||
|
### Найдите запись результата
|
||||||
|
|
||||||
|
Финальный `INSERT` берет поля из `fact_src`. Условие `NOT EXISTS`
|
||||||
|
проверяет пару `(ticket_no, flight_id)` и пропускает уже записанные строки.
|
||||||
|
Вернитесь к `UPDATE` в начале файла: он меняет только цену, место,
|
||||||
|
признак посадки и метки загрузки у существующих строк.
|
||||||
|
Ключи измерений он не пересчитывает.
|
||||||
|
|
||||||
|
Поэтому после реализации измерений нужен
|
||||||
|
[пересчет факта по ТЗ](../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений).
|
||||||
|
Проверки результата находятся в [fact_flight_sales_dq.sql](../../sql/dds/fact_flight_sales_dq.sql).
|
||||||
|
|
||||||
## Как проверить свой слой
|
## Как проверить свой слой
|
||||||
|
|
||||||
- Выпишите все таблицы из `FROM` и `JOIN` своего `*_load.sql`.
|
Перед записью SQL найдите свой объект на карте и откройте его пункт ТЗ.
|
||||||
- Сравните список с эталонной схемой или дизайн-документом этого слоя.
|
Сформулируйте, что означает одна строка результата, по каким полям соединяются
|
||||||
- Проверьте служебные и SCD-поля по [`naming_conventions.md`](naming_conventions.md).
|
источники и что произойдет при отсутствии совпадения. Затем сверяйте
|
||||||
- Убедитесь, что рядом есть `*_dq.sql` и он покрывает нужные
|
`INSERT` с DDL, а преобразования и `*_dq.sql` с требованиями задания.
|
||||||
[классы DQ-проверок](../reference/dq_taxonomy.md).
|
|
||||||
|
|
||||||
## Шаблон схемы одной таблицы
|
Близкие готовые примеры для следующих этапов:
|
||||||
|
|
||||||
Скопируйте шаблон и замените имена таблиц и полей:
|
- SCD1: [dim_airports_load.sql](../../sql/dds/dim_airports_load.sql) обновляет
|
||||||
|
изменившиеся атрибуты через `IS DISTINCT FROM` и вставляет новые бизнес-ключи.
|
||||||
|
- DM: [sales_report_load.sql](../../sql/dm/sales_report_load.sql) находит
|
||||||
|
затронутые даты по `_load_ts` факта, пересчитывает за них полные агрегаты
|
||||||
|
во временную таблицу и выполняет `UPDATE` и `INSERT`.
|
||||||
|
|
||||||
```mermaid
|
[Порядок заданий](../assignment/README.md#следующие-этапы) ведет к требованиям
|
||||||
graph LR
|
каждого этапа. Имена служебных полей проверяйте по
|
||||||
MAIN["schema.main_input"] -->|"grain, metric"| TARGET["schema.target"]
|
[соглашениям](naming_conventions.md), состав DQ - по
|
||||||
LOOKUP["schema.lookup"] -.->|"lookup_key"| TARGET
|
[справочнику проверок](../reference/dq_taxonomy.md).
|
||||||
```
|
|
||||||
Reference in new issue
Block a user