docs: обновлена документация — airport lookup через ods.routes

Описания fact_flight_sales_load обновлены в bookings_dds_design,
bookings_to_gp_dds и db_schema: departure/arrival_airport_sk теперь
разрешаются через ods.routes → dim_airports, а не через dim_routes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:51:55 +03:00
co-authored by Claude Opus 4.6
parent f053fe1e35
commit a8148c8afa
3 changed files with 37 additions and 18 deletions
+6 -2
View File
@@ -123,8 +123,12 @@ DQ-проверки факта отражают эту логику:
> В боевых системах для этого используют «строку-заглушку» (unknown member, SK = 0)
> и отдельный процесс backfill.
**Point-in-time join для SCD2.**
Для `dim_routes` используется привязка по дате рейса:
**Два пути lookup для аэропортов и маршрутов.**
Аэропорты (`departure_airport_sk`, `arrival_airport_sk`) разрешаются через `ods.routes`
`dim_airports`. Аэропорты вылета/прилёта одинаковы во всех версиях маршрута, поэтому
point-in-time логика не нужна — безопасно брать актуальную версию из ODS.
`route_sk` и `airplane_sk` разрешаются через point-in-time join с SCD2 `dim_routes`:
```sql
LEFT JOIN dds.dim_routes AS rte
+25 -11
View File
@@ -168,13 +168,14 @@ DISTRIBUTED BY (ticket_no)
### 3.8. Политика NULL FK в факте
FK суррогатные ключи разделены на три группы:
FK суррогатные ключи разделены на четыре группы по источнику lookup:
| Группа | FK | NULL допустим? | Причина |
|--------|-----|---------------|---------|
| **Обязательные** | `tariff_sk`, `passenger_sk` | Нет | Данные всегда есть в ODS (segments, tickets). NULL = баг загрузки. |
| **Зависят от маршрута** | `route_sk`, `departure_airport_sk`, `arrival_airport_sk`, `airplane_sk` | Нет (в норме) | Маршрут должен быть в ODS. NULL = аномалия данных, DQ предупреждает. |
| **Зависят от расписания** | `calendar_sk` | Допустим (редко) | `scheduled_departure` может быть NULL в ODS. DQ считает и логирует, но не фейлит. |
| Группа | FK | Lookup-путь | NULL допустим? | Причина |
|--------|-----|-------------|---------------|---------|
| **Обязательные** | `tariff_sk`, `passenger_sk` | `dim_tariffs` / `dim_passengers` (SCD1) | Нет | Данные всегда есть в ODS. NULL = баг загрузки. |
| **Эталонный справочник** | `departure_airport_sk`, `arrival_airport_sk` | `ods.routes``dim_airports` | Нет (в норме) | Аэропорты разрешаются через `ods.routes` (эталон), не через `dim_routes`. NULL = аномалия данных, DQ проверяет с порогом 1%. |
| **Point-in-time SCD2** | `route_sk`, `airplane_sk` | `dim_routes` (SCD2 lookup) | Нет (в норме) | Point-in-time привязка к версии маршрута на дату рейса. NULL = аномалия, DQ предупреждает. |
| **Расписание** | `calendar_sk` | `dim_calendar` | Допустим (редко) | `scheduled_departure` может быть NULL в ODS. DQ считает и логирует. |
DQ-проверки явно контролируют каждую группу (см. секцию 6).
@@ -459,15 +460,27 @@ WITH fact_src AS (
JOIN ods.tickets AS tkt ON tkt.ticket_no = seg.ticket_no
JOIN ods.bookings AS bkg ON bkg.book_ref = tkt.book_ref
JOIN ods.flights AS flt ON flt.flight_id = seg.flight_id
-- Airport lookup через ods.routes (эталонный справочник).
-- Аэропорты вылета/прилёта одинаковы во всех версиях маршрута —
-- безопасно брать из ODS без point-in-time логики.
LEFT JOIN (
SELECT route_no, departure_airport, arrival_airport
FROM (
SELECT route_no, departure_airport, arrival_airport,
ROW_NUMBER() OVER (PARTITION BY route_no ORDER BY validity DESC) AS rn
FROM ods.routes
) ranked
WHERE rn = 1
) AS ods_rte ON ods_rte.route_no = flt.route_no
-- SCD2 point-in-time: версия маршрута, актуальная на дату вылета
LEFT JOIN dds.dim_routes AS rte
ON rte.route_bk = flt.route_no
AND flt.scheduled_departure::DATE >= rte.valid_from
AND (rte.valid_to IS NULL OR flt.scheduled_departure::DATE < rte.valid_to)
LEFT JOIN dds.dim_calendar AS cal ON cal.date_actual = flt.scheduled_departure::DATE
LEFT JOIN dds.dim_airports AS dep ON dep.airport_bk = rte.departure_airport
LEFT JOIN dds.dim_airports AS arr ON arr.airport_bk = rte.arrival_airport
LEFT JOIN dds.dim_airplanes AS ap ON ap.airplane_bk = rte.airplane_code
LEFT JOIN dds.dim_airports AS dep ON dep.airport_bk = ods_rte.departure_airport -- через ods.routes
LEFT JOIN dds.dim_airports AS arr ON arr.airport_bk = ods_rte.arrival_airport -- через ods.routes
LEFT JOIN dds.dim_airplanes AS ap ON ap.airplane_bk = rte.airplane_code -- через dim_routes
LEFT JOIN dds.dim_tariffs AS tar ON tar.fare_conditions = seg.fare_conditions
LEFT JOIN dds.dim_passengers AS pax ON pax.passenger_bk = tkt.passenger_id
LEFT JOIN ods.boarding_passes AS bp
@@ -497,8 +510,9 @@ ANALYZE dds.fact_flight_sales;
### 5.5. Модель историчности факта
Dimension SK фиксируются **при INSERT** и не перезаписываются:
- `route_sk` — версия маршрута на дату `scheduled_departure` (point-in-time SCD2 lookup);
- `departure_airport_sk`, `arrival_airport_sk`, `airplane_sk` — из той же версии маршрута;
- `departure_airport_sk`, `arrival_airport_sk` — из `ods.routes``dim_airports` (эталонный справочник; аэропорты одинаковы для всех версий маршрута);
- `route_sk` — версия маршрута на дату `scheduled_departure` (point-in-time SCD2 lookup через `dim_routes`);
- `airplane_sk` — из той же point-in-time версии `dim_routes`;
- `calendar_sk`, `tariff_sk`, `passenger_sk` — из текущих SCD1-измерений на момент INSERT.
UPDATE факта обновляет только **мутабельные поля**: `is_boarded`, `seat_no`, `price`
+6 -5
View File
@@ -115,6 +115,7 @@ graph LR
ODS_Tickets -->|book_ref, passenger_id| FACT_Sales
ODS_Bookings -->|book_date| FACT_Sales
ODS_Flights -->|schedule, route_no| FACT_Sales
ODS_Routes -->|dep/arr airports| FACT_Sales
ODS_Boarding -->|LEFT JOIN seat_no| FACT_Sales
%% Dimensions to Fact
@@ -220,12 +221,12 @@ SK генерация: `MAX(sk) + ROW_NUMBER()` (безопасно при `conc
`dds.fact_flight_sales` — зерно: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`).
| FK | Источник | Примечание |
|----|----------|------------|
| FK | Lookup-путь | Примечание |
|----|-------------|------------|
| `calendar_sk` | `dim_calendar` | Дата вылета |
| `departure_airport_sk` | `dim_airports` | Аэропорт вылета |
| `arrival_airport_sk` | `dim_airports` | Аэропорт прилёта |
| `airplane_sk` | `dim_airplanes` | Самолёт |
| `departure_airport_sk` | `ods.routes``dim_airports` | Аэропорт вылета (эталонный справочник) |
| `arrival_airport_sk` | `ods.routes``dim_airports` | Аэропорт прилёта (эталонный справочник) |
| `airplane_sk` | `dim_routes``dim_airplanes` | Самолёт (point-in-time SCD2) |
| `tariff_sk` | `dim_tariffs` | Тариф |
| `passenger_sk` | `dim_passengers` | Пассажир |
| `route_sk` | `dim_routes` | Версия маршрута (SCD2, point-in-time) |