Files
airflow-greenplum/sql/dds/fact_flight_sales_load.sql
T
ddadmin 22a0590e1b feat(sql): явное управление storage и автоматизированный E2E-тест через REST API
- Зачем:
  - необходимо визуализировать выбор типа хранения (Heap vs Append-Only) для учебных целей.
  - автоматизировать проверку всей цепочки DWH для исключения ручных ошибок.
  - сделать процесс отладки прозрачным и наглядным через стандартные инструменты Airflow.
- Что:
  - внедрена клауза WITH (appendonly=...) во все DDL; ODS-справочники переведены на AO Row и TRUNCATE+INSERT.
  - создан скрипт scripts/e2e_etl.sh для полного прогона ETL (DDL + 2 дня данных) через REST API.
  - исправлены баги типизации (INTEGER[]), именования полей (amount, passenger_id) и удалены фантомные колонки (contact_data).
  - обновлен e2e-etl-test-protocol.md: добавлен раздел по отладке, чтению логов и перезапуску задач через API.
  - исправлены pytest-контракты под новую логику загрузки.
- Проверка:
  - успешный прогон `make e2e-smoke` (полный цикл от очистки до витрины).
2026-03-03 22:38:26 +03:00

115 lines
3.6 KiB
SQL

-- Загрузка DDS fact_flight_sales: инкрементальный UPSERT по (ticket_no, flight_id).
-- Statement 1: UPDATE существующих строк факта.
-- Обновляем только мутабельные поля; SK измерений не перезаписываем.
UPDATE dds.fact_flight_sales AS f
SET seat_no = bp.seat_no,
price = seg.amount,
is_boarded = (bp.ticket_no IS NOT NULL),
_load_id = '{{ run_id }}',
_load_ts = now()
FROM ods.segments AS seg
LEFT JOIN ods.boarding_passes AS bp
ON bp.ticket_no = seg.ticket_no
AND bp.flight_id = seg.flight_id
WHERE f.ticket_no = seg.ticket_no
AND f.flight_id = seg.flight_id
AND (
f.is_boarded IS DISTINCT FROM (bp.ticket_no IS NOT NULL)
OR f.price IS DISTINCT FROM seg.amount
OR f.seat_no IS DISTINCT FROM bp.seat_no
);
-- Statement 2: INSERT новых строк факта.
-- Dimension SK фиксируются на момент вставки (point-in-time для SCD2 routes).
WITH fact_src AS (
SELECT
seg.ticket_no,
seg.flight_id,
cal.calendar_sk,
dep.airport_sk AS departure_airport_sk,
arr.airport_sk AS arrival_airport_sk,
ap.airplane_sk,
tar.tariff_sk,
pax.passenger_sk,
rte.route_sk,
tkt.book_ref,
bkg.book_date::DATE AS book_date,
bp.seat_no,
seg.amount AS price,
(bp.ticket_no IS NOT NULL) AS is_boarded
FROM ods.segments AS seg
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
-- Учебный комментарий: Late-arriving dimensions (Опаздывающие измерения)
-- Мы используем LEFT JOIN, так как факт (рейс/билет) может прийти раньше,
-- чем справочник (пассажир/маршрут) обновится в DDS.
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_tariffs AS tar
ON tar.fare_conditions = seg.fare_conditions
LEFT JOIN dds.dim_passengers AS pax
ON pax.passenger_id = tkt.passenger_id
LEFT JOIN ods.boarding_passes AS bp
ON bp.ticket_no = seg.ticket_no
AND bp.flight_id = seg.flight_id
)
INSERT INTO dds.fact_flight_sales (
calendar_sk,
departure_airport_sk,
arrival_airport_sk,
airplane_sk,
tariff_sk,
passenger_sk,
route_sk,
book_ref,
ticket_no,
flight_id,
book_date,
seat_no,
price,
is_boarded,
_load_id,
_load_ts
)
SELECT
s.calendar_sk,
s.departure_airport_sk,
s.arrival_airport_sk,
s.airplane_sk,
s.tariff_sk,
s.passenger_sk,
s.route_sk,
s.book_ref,
s.ticket_no,
s.flight_id,
s.book_date,
s.seat_no,
s.price,
s.is_boarded,
'{{ run_id }}',
now()
FROM fact_src AS s
WHERE NOT EXISTS (
SELECT 1
FROM dds.fact_flight_sales AS f
WHERE f.ticket_no = s.ticket_no
AND f.flight_id = s.flight_id
);
ANALYZE dds.fact_flight_sales;