Files
airflow-greenplum/sql/dm/sales_report_dq.sql
T
ddadmin 84ec949ff9 refactor(dm): переход на batch-driven инкрементальность для sales_report
- Зачем:
  - жесткая привязка инкремента к логической дате Airflow ({{ ds }}) приводила к пустой витрине при обработке исторических и "опоздавших" (late-arriving) данных.
- Что:
  - изменена фильтрация в скрипте загрузки витрины: теперь динамически определяются даты, затронутые текущим батчем (через _load_id).
  - обновлены DQ-проверки для валидации только тех дат, которые были изменены в рамках запущенного батча.
  - в дизайн-документ добавлено описание паттерна работы с late-arriving facts для студентов.
- Проверка:
  - запуск пайплайна "с нуля" за логическую дату 2024-01-01 приводит к корректному расчету агрегатов для исторических данных 2017 года (>8000 строк).
2026-03-01 01:54:57 +03:00

142 lines
5.6 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- DQ для DM витрины sales_report.
--
-- Проверки:
-- 1. Таблица не пуста (если источник за затронутые батчем дни не пуст)
-- 2. Нет дублей по составному ключу
-- 3. Бизнес-инварианты: tickets_sold >= passengers_boarded, boarding_rate BETWEEN 0 AND 1
-- 4. Обязательные поля не NULL
--
-- ВАЖНО (Паттерн Data Quality):
-- Проверки выполняются ИНКРЕМЕНТАЛЬНО. Мы находим все даты, измененные текущим батчем,
-- и валидируем витрину только для этих дат.
DO $$
DECLARE
v_row_count BIGINT;
v_src_count BIGINT;
v_dup_count BIGINT;
v_invalid_boarding BIGINT;
v_null_required BIGINT;
BEGIN
-- Создаем временную таблицу с датами, которые мы затронули в этом батче.
CREATE TEMP TABLE tmp_dq_affected_dates ON COMMIT DROP AS
SELECT DISTINCT cal.date_actual
FROM dds.fact_flight_sales AS f
JOIN dds.dim_calendar AS cal ON f.calendar_sk = cal.calendar_sk
WHERE f._load_id = '{{ run_id }}';
-- Если батч пуст (нет новых или измененных фактов), нам нечего валидировать.
IF NOT EXISTS (SELECT 1 FROM tmp_dq_affected_dates) THEN
RAISE NOTICE 'DQ PASSED: Источник не содержит новых данных для витрины в батче %.', '{{ run_id }}';
RETURN;
END IF;
-- Проверка 1: Таблица не пуста при непустом источнике (для затронутых дат)
SELECT COUNT(*)
INTO v_src_count
FROM dds.fact_flight_sales AS f
JOIN dds.dim_calendar AS cal
ON cal.calendar_sk = f.calendar_sk
WHERE cal.date_actual IN (SELECT date_actual FROM tmp_dq_affected_dates);
SELECT COUNT(*)
INTO v_row_count
FROM dm.sales_report
WHERE flight_date IN (SELECT date_actual FROM tmp_dq_affected_dates);
IF v_src_count > 0 AND v_row_count = 0 THEN
RAISE EXCEPTION
'DQ FAILED: dm.sales_report пуста для дат батча при непустом источнике (% строк в fact_flight_sales)',
v_src_count;
END IF;
RAISE NOTICE 'DQ INFO: dm.sales_report содержит % строк для дат текущего батча', v_row_count;
-- Проверка 2: Нет дублей по составному ключу (в рамках затронутых дат)
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 IN (SELECT date_actual FROM tmp_dq_affected_dates)
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',
v_dup_count;
END IF;
RAISE NOTICE 'DQ PASSED: Дублей по составному ключу для дат батча нет';
-- Проверка 3: Бизнес-инварианты (в рамках затронутых дат)
-- tickets_sold >= passengers_boarded
SELECT COUNT(*)
INTO v_invalid_boarding
FROM dm.sales_report
WHERE flight_date IN (SELECT date_actual FROM tmp_dq_affected_dates)
AND tickets_sold < passengers_boarded;
IF v_invalid_boarding <> 0 THEN
RAISE EXCEPTION
'DQ FAILED: % строк с tickets_sold < passengers_boarded',
v_invalid_boarding;
END IF;
-- boarding_rate BETWEEN 0 AND 1
SELECT COUNT(*)
INTO v_invalid_boarding
FROM dm.sales_report
WHERE flight_date IN (SELECT date_actual FROM tmp_dq_affected_dates)
AND (boarding_rate < 0 OR boarding_rate > 1);
IF v_invalid_boarding <> 0 THEN
RAISE EXCEPTION
'DQ FAILED: % строк с boarding_rate вне диапазона [0, 1]',
v_invalid_boarding;
END IF;
-- total_revenue >= 0
SELECT COUNT(*)
INTO v_invalid_boarding
FROM dm.sales_report
WHERE flight_date IN (SELECT date_actual FROM tmp_dq_affected_dates)
AND total_revenue < 0;
IF v_invalid_boarding <> 0 THEN
RAISE EXCEPTION
'DQ FAILED: % строк с отрицательной total_revenue',
v_invalid_boarding;
END IF;
RAISE NOTICE 'DQ PASSED: Бизнес-инварианты соблюдены';
-- Проверка 4: Обязательные поля не NULL (в рамках затронутых дат)
SELECT COUNT(*)
INTO v_null_required
FROM dm.sales_report
WHERE flight_date IN (SELECT date_actual FROM tmp_dq_affected_dates)
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 в обязательных полях',
v_null_required;
END IF;
RAISE NOTICE 'DQ PASSED: Обязательные поля заполнены';
-- Итог
RAISE NOTICE 'DQ COMPLETE: dm.sales_report прошла все проверки для текущего батча';
END $$;