refactor(dm): исправление архитектуры витрины sales_report
- Зачем:
- исходная реализация содержала критические ошибки MPP (Load/Processing Skew) и Full Scan.
- требуется демонстрация студентам эталонного инкрементального UPSERT.
- Что:
- изменен ключ дистрибуции с flight_date на (departure_airport_sk, arrival_airport_sk).
- внедрен каноничный UPSERT через TEMP TABLE (агрегация выполняется один раз).
- добавлен инкрементальный фильтр по {{ ds }} для предотвращения Full Scan dds.fact_flight_sales.
- проверки DQ переведены в инкрементальный режим (валидация только текущего батча).
- Проверка:
- airflow dags test bookings_to_gp_dm 2026-02-28.
This commit is contained in:
+43
-29
@@ -1,10 +1,14 @@
|
||||
-- DQ для DM витрины sales_report.
|
||||
--
|
||||
-- Проверки:
|
||||
-- 1. Таблица не пуста (если источник не пуст)
|
||||
-- 1. Таблица не пуста (если источник за день {{ ds }} не пуст)
|
||||
-- 2. Нет дублей по составному ключу (flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk)
|
||||
-- 3. Бизнес-инварианты: tickets_sold >= passengers_boarded, boarding_rate BETWEEN 0 AND 1
|
||||
-- 4. Обязательные поля не NULL
|
||||
--
|
||||
-- ВАЖНО (Паттерн Data Quality):
|
||||
-- Все проверки выполняются ИНКРЕМЕНТАЛЬНО (только для данных за '{{ ds }}'::date).
|
||||
-- Валидировать терабайты исторических данных при каждой загрузке недопустимо.
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
@@ -14,63 +18,69 @@ DECLARE
|
||||
v_invalid_boarding BIGINT;
|
||||
v_null_required BIGINT;
|
||||
BEGIN
|
||||
-- Проверка 1: Таблица не пуста при непустом источнике
|
||||
-- Проверка 1: Таблица не пуста при непустом источнике (инкрементально)
|
||||
SELECT COUNT(*)
|
||||
INTO v_src_count
|
||||
FROM dds.fact_flight_sales;
|
||||
FROM dds.fact_flight_sales AS f
|
||||
JOIN dds.dim_calendar AS cal
|
||||
ON cal.calendar_sk = f.calendar_sk
|
||||
WHERE cal.date_actual = '{{ ds }}'::date;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_row_count
|
||||
FROM dm.sales_report;
|
||||
FROM dm.sales_report
|
||||
WHERE flight_date = '{{ ds }}'::date;
|
||||
|
||||
-- Если источник пуст, допускаем пустую витрину (инкрементальное окно без данных)
|
||||
-- Если источник за день пуст, допускаем пустую витрину
|
||||
IF v_src_count = 0 THEN
|
||||
IF v_row_count <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: fact_flight_sales пуст, но dm.sales_report содержит % строк',
|
||||
'DQ FAILED: fact_flight_sales за {{ ds }} пуст, но dm.sales_report содержит % строк',
|
||||
v_row_count;
|
||||
END IF;
|
||||
RAISE NOTICE 'DQ PASSED: Источник и витрина пусты (нет данных для обработки).';
|
||||
RAISE NOTICE 'DQ PASSED: Источник и витрина за {{ ds }} пусты (нет данных для обработки).';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_row_count = 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: dm.sales_report пуста при непустом источнике (% строк в fact_flight_sales)',
|
||||
'DQ FAILED: dm.sales_report за {{ ds }} пуста при непустом источнике (% строк в fact_flight_sales)',
|
||||
v_src_count;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'DQ INFO: dm.sales_report содержит % строк', v_row_count;
|
||||
RAISE NOTICE 'DQ INFO: dm.sales_report за {{ ds }} содержит % строк', v_row_count;
|
||||
|
||||
-- Проверка 2: Нет дублей по составному ключу
|
||||
-- Проверка 2: Нет дублей по составному ключу (в рамках партиции {{ ds }})
|
||||
SELECT COUNT(*)
|
||||
INTO v_dup_count
|
||||
FROM (
|
||||
SELECT flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk
|
||||
FROM dm.sales_report
|
||||
WHERE flight_date = '{{ ds }}'::date
|
||||
GROUP BY flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk
|
||||
HAVING COUNT(*) > 1
|
||||
) AS dups;
|
||||
|
||||
IF v_dup_count <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: Найдено % дублирующихся комбинаций ключа в dm.sales_report',
|
||||
'DQ FAILED: Найдено % дублирующихся комбинаций ключа в dm.sales_report за {{ ds }}',
|
||||
v_dup_count;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'DQ PASSED: Дублей по составному ключу нет';
|
||||
RAISE NOTICE 'DQ PASSED: Дублей по составному ключу за день нет';
|
||||
|
||||
-- Проверка 3: Бизнес-инварианты
|
||||
-- Проверка 3: Бизнес-инварианты (в рамках партиции {{ ds }})
|
||||
|
||||
-- tickets_sold >= passengers_boarded
|
||||
SELECT COUNT(*)
|
||||
INTO v_invalid_boarding
|
||||
FROM dm.sales_report
|
||||
WHERE tickets_sold < passengers_boarded;
|
||||
WHERE flight_date = '{{ ds }}'::date
|
||||
AND tickets_sold < passengers_boarded;
|
||||
|
||||
IF v_invalid_boarding <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: % строк с tickets_sold < passengers_boarded',
|
||||
'DQ FAILED: % строк с tickets_sold < passengers_boarded за {{ ds }}',
|
||||
v_invalid_boarding;
|
||||
END IF;
|
||||
|
||||
@@ -78,11 +88,12 @@ BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_invalid_boarding
|
||||
FROM dm.sales_report
|
||||
WHERE boarding_rate < 0 OR boarding_rate > 1;
|
||||
WHERE flight_date = '{{ ds }}'::date
|
||||
AND (boarding_rate < 0 OR boarding_rate > 1);
|
||||
|
||||
IF v_invalid_boarding <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: % строк с boarding_rate вне диапазона [0, 1]',
|
||||
'DQ FAILED: % строк с boarding_rate вне диапазона [0, 1] за {{ ds }}',
|
||||
v_invalid_boarding;
|
||||
END IF;
|
||||
|
||||
@@ -90,36 +101,39 @@ BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_invalid_boarding
|
||||
FROM dm.sales_report
|
||||
WHERE total_revenue < 0;
|
||||
WHERE flight_date = '{{ ds }}'::date
|
||||
AND total_revenue < 0;
|
||||
|
||||
IF v_invalid_boarding <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: % строк с отрицательной total_revenue',
|
||||
'DQ FAILED: % строк с отрицательной total_revenue за {{ ds }}',
|
||||
v_invalid_boarding;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'DQ PASSED: Бизнес-инварианты соблюдены';
|
||||
|
||||
-- Проверка 4: Обязательные поля не NULL
|
||||
-- Проверка 4: Обязательные поля не NULL (в рамках партиции {{ ds }})
|
||||
SELECT COUNT(*)
|
||||
INTO v_null_required
|
||||
FROM dm.sales_report
|
||||
WHERE flight_date IS NULL
|
||||
OR departure_airport_sk IS NULL
|
||||
OR arrival_airport_sk IS NULL
|
||||
OR tariff_sk IS NULL
|
||||
OR tickets_sold IS NULL
|
||||
OR total_revenue IS NULL
|
||||
OR boarding_rate IS NULL;
|
||||
WHERE flight_date = '{{ ds }}'::date
|
||||
AND (
|
||||
departure_airport_sk IS NULL
|
||||
OR arrival_airport_sk IS NULL
|
||||
OR tariff_sk IS NULL
|
||||
OR tickets_sold IS NULL
|
||||
OR total_revenue IS NULL
|
||||
OR boarding_rate IS NULL
|
||||
);
|
||||
|
||||
IF v_null_required <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: % строк с NULL в обязательных полях',
|
||||
'DQ FAILED: % строк с NULL в обязательных полях за {{ ds }}',
|
||||
v_null_required;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'DQ PASSED: Обязательные поля заполнены';
|
||||
|
||||
-- Итог
|
||||
RAISE NOTICE 'DQ COMPLETE: dm.sales_report прошла все проверки';
|
||||
RAISE NOTICE 'DQ COMPLETE: dm.sales_report прошла все проверки за {{ ds }}';
|
||||
END $$;
|
||||
|
||||
Reference in New Issue
Block a user