feat(dm): добавлена витрина airport_traffic и внедрен паттерн dual-role dimensions
- Зачем: - необходимо продемонстрировать студентам работу с одной сущностью в разных ролях (вылет/прилет) через UNION ALL. - Что: - созданы DDL, Load и DQ скрипты для витрины dm.airport_traffic (пассажиропоток аэропортов). - реализован паттерн Unpivot через UNION ALL для консолидации метрик вылета и прилета в одном разрезе. - внедрена инкрементальная загрузка по затронутым датам (HWM) с честным подсчетом рейсов через COUNT(DISTINCT). - добавлены подробные комментарии к колонкам выручки, предупреждающие о риске двойного счета. - обновлены DAGи bookings_dm_ddl и bookings_to_gp_dm для включения витрины в общий пайплайн. - Проверка: - визуальный аудит SQL-кода на предмет использования airport_bk и корректной агрегации по ролям. - наличие DQ-инварианта total_passengers = departures + arrivals. - верификация блока UPDATE: теперь обновляются и денормализованные атрибуты (city, airport_bk).
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
-- Загрузка витрины dm.airport_traffic: инкрементальный UPSERT по датам.
|
||||
--
|
||||
-- Учебные цели:
|
||||
-- 1. Обработка dual-role dimensions через UNION ALL:
|
||||
-- Мы превращаем один билет (факт) в два "события": вылет и прилет.
|
||||
-- Это позволяет собрать единую статистику аэропорта в одном проходе.
|
||||
-- 2. Инкремент по датам (HWM): в этой витрине метрики ограничены сутками,
|
||||
-- поэтому пересчитываем только те дни, где появились новые факты.
|
||||
-- 3. Агрегация рейсов: используем COUNT(DISTINCT flight_id) для подсчета рейсов.
|
||||
-- 4. Денормализация (SCD1): обновление атрибутов (город) при изменениях в измерении.
|
||||
|
||||
-- Шаг 1: Находим даты, затронутые новыми/измененными фактами.
|
||||
CREATE TEMP TABLE tmp_traffic_affected_dates ON COMMIT DROP AS
|
||||
SELECT DISTINCT cal.date_actual AS traffic_date
|
||||
FROM dds.fact_flight_sales f
|
||||
JOIN dds.dim_calendar cal ON f.calendar_sk = cal.calendar_sk
|
||||
WHERE f._load_ts > (
|
||||
SELECT COALESCE(MAX(_load_ts), '1900-01-01'::TIMESTAMP)
|
||||
FROM dm.airport_traffic
|
||||
);
|
||||
|
||||
-- Шаг 2: Unpivot (UNION ALL) и агрегация по затронутым датам.
|
||||
CREATE TEMP TABLE tmp_airport_traffic_delta ON COMMIT DROP AS
|
||||
WITH raw_events AS (
|
||||
-- Роль 1: Аэропорт вылета
|
||||
SELECT
|
||||
cal.date_actual AS traffic_date,
|
||||
f.departure_airport_sk AS airport_sk,
|
||||
f.flight_id,
|
||||
'departure' AS role,
|
||||
(CASE WHEN f.is_boarded THEN 1 ELSE 0 END) AS is_passenger,
|
||||
f.price AS revenue
|
||||
FROM dds.fact_flight_sales f
|
||||
JOIN dds.dim_calendar cal ON f.calendar_sk = cal.calendar_sk
|
||||
WHERE cal.date_actual IN (SELECT traffic_date FROM tmp_traffic_affected_dates)
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Роль 2: Аэропорт прилета
|
||||
SELECT
|
||||
cal.date_actual AS traffic_date,
|
||||
f.arrival_airport_sk AS airport_sk,
|
||||
f.flight_id,
|
||||
'arrival' AS role,
|
||||
(CASE WHEN f.is_boarded THEN 1 ELSE 0 END) AS is_passenger,
|
||||
f.price AS revenue
|
||||
FROM dds.fact_flight_sales f
|
||||
JOIN dds.dim_calendar cal ON f.calendar_sk = cal.calendar_sk
|
||||
WHERE cal.date_actual IN (SELECT traffic_date FROM tmp_traffic_affected_dates)
|
||||
)
|
||||
SELECT
|
||||
e.traffic_date,
|
||||
e.airport_sk,
|
||||
a.airport_bk, -- Берем из dim_airports.airport_bk
|
||||
a.city,
|
||||
-- Агрегация вылетов
|
||||
COUNT(DISTINCT CASE WHEN e.role = 'departure' THEN e.flight_id END) AS departures_flights,
|
||||
SUM(CASE WHEN e.role = 'departure' THEN e.is_passenger ELSE 0 END) AS departures_passengers,
|
||||
SUM(CASE WHEN e.role = 'departure' THEN e.revenue ELSE 0 END) AS departures_revenue,
|
||||
-- Агрегация прилетов
|
||||
COUNT(DISTINCT CASE WHEN e.role = 'arrival' THEN e.flight_id END) AS arrivals_flights,
|
||||
SUM(CASE WHEN e.role = 'arrival' THEN e.is_passenger ELSE 0 END) AS arrivals_passengers,
|
||||
SUM(CASE WHEN e.role = 'arrival' THEN e.revenue ELSE 0 END) AS arrivals_revenue,
|
||||
-- Итого: все пассажиры (сели + вышли в этом аэропорту)
|
||||
SUM(e.is_passenger) AS total_passengers
|
||||
FROM raw_events e
|
||||
JOIN dds.dim_airports a ON e.airport_sk = a.airport_sk
|
||||
GROUP BY e.traffic_date, e.airport_sk, a.airport_bk, a.city;
|
||||
|
||||
-- Шаг 3: UPDATE существующих записей (включая денормализованные атрибуты).
|
||||
UPDATE dm.airport_traffic AS tgt
|
||||
SET
|
||||
airport_bk = src.airport_bk,
|
||||
city = src.city,
|
||||
departures_flights = src.departures_flights,
|
||||
departures_passengers = src.departures_passengers,
|
||||
departures_revenue = src.departures_revenue,
|
||||
arrivals_flights = src.arrivals_flights,
|
||||
arrivals_passengers = src.arrivals_passengers,
|
||||
arrivals_revenue = src.arrivals_revenue,
|
||||
total_passengers = src.total_passengers,
|
||||
updated_at = now(),
|
||||
_load_id = '{{ run_id }}',
|
||||
_load_ts = now()
|
||||
FROM tmp_airport_traffic_delta AS src
|
||||
WHERE tgt.traffic_date = src.traffic_date
|
||||
AND tgt.airport_sk = src.airport_sk
|
||||
AND (
|
||||
tgt.total_passengers IS DISTINCT FROM src.total_passengers
|
||||
OR tgt.departures_flights IS DISTINCT FROM src.departures_flights
|
||||
OR tgt.arrivals_flights IS DISTINCT FROM src.arrivals_flights
|
||||
OR tgt.city IS DISTINCT FROM src.city
|
||||
OR tgt.airport_bk IS DISTINCT FROM src.airport_bk
|
||||
);
|
||||
|
||||
-- Шаг 4: INSERT новых записей.
|
||||
INSERT INTO dm.airport_traffic (
|
||||
traffic_date,
|
||||
airport_sk,
|
||||
airport_bk,
|
||||
city,
|
||||
departures_flights,
|
||||
departures_passengers,
|
||||
departures_revenue,
|
||||
arrivals_flights,
|
||||
arrivals_passengers,
|
||||
arrivals_revenue,
|
||||
total_passengers,
|
||||
_load_id
|
||||
)
|
||||
SELECT
|
||||
src.traffic_date,
|
||||
src.airport_sk,
|
||||
src.airport_bk,
|
||||
src.city,
|
||||
src.departures_flights,
|
||||
src.departures_passengers,
|
||||
src.departures_revenue,
|
||||
src.arrivals_flights,
|
||||
src.arrivals_passengers,
|
||||
src.arrivals_revenue,
|
||||
src.total_passengers,
|
||||
'{{ run_id }}' AS _load_id
|
||||
FROM tmp_airport_traffic_delta AS src
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM dm.airport_traffic AS tgt
|
||||
WHERE tgt.traffic_date = src.traffic_date
|
||||
AND tgt.airport_sk = src.airport_sk
|
||||
);
|
||||
Reference in New Issue
Block a user