From d4c91c4eec68716ba75e0bd698becaa922b4d020 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 18 Jan 2026 21:48:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D1=83=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D1=83=D1=81=D1=82=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=BC=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/bookings_to_gp_stage.md | 4 ++-- docs/internal/bookings_stg_code_review.md | 6 +++++- docs/internal/db_schema.md | 8 ++++---- sql/stg/bookings_dq.sql | 22 +++++++++++++++++++--- sql/stg/flights_dq.sql | 22 +++++++++++++++++++--- sql/stg/segments_dq.sql | 22 +++++++++++++++++++--- sql/stg/tickets_dq.sql | 22 +++++++++++++++++++--- 7 files changed, 87 insertions(+), 19 deletions(-) diff --git a/docs/bookings_to_gp_stage.md b/docs/bookings_to_gp_stage.md index 71678ba..70d6428 100644 --- a/docs/bookings_to_gp_stage.md +++ b/docs/bookings_to_gp_stage.md @@ -100,8 +100,8 @@ make bookings-init - `load_segments_to_stg` → `check_segments_dq` (инкремент по `book_date` через tickets/bookings) - `load_boarding_passes_to_stg` → `check_boarding_passes_dq` (full snapshot) -Важно: для некоторых таблиц “пустое окно инкремента” считается ошибкой (DQ делает `RAISE EXCEPTION`), -а для `boarding_passes` DQ может быть пропущена, если в источнике 0 строк. +Важно: для инкрементальных таблиц “пустое окно инкремента” допустимо — загрузка и DQ логируют `NOTICE` и завершаются успешно. +Для snapshot-справочников (airports/airplanes/routes/seats) пустой источник считается ошибкой (DQ делает `RAISE EXCEPTION`). 8) `finish_summary` diff --git a/docs/internal/bookings_stg_code_review.md b/docs/internal/bookings_stg_code_review.md index 83ea3bf..a1786be 100644 --- a/docs/internal/bookings_stg_code_review.md +++ b/docs/internal/bookings_stg_code_review.md @@ -81,7 +81,11 @@ - **Fail** полезен, когда “ожидаем данные в каждом запуске” (например, учебный генератор должен добавлять день); - **Skip** полезен, когда “окно может быть пустым и это нормально”. -Рекомендация: зафиксировать политику в документации и сделать поведение единообразным по группам таблиц. +Выбранная политика для учебного стенда: +- Для инкрементальных таблиц (`bookings`, `tickets`, `flights`, `segments`) “пустое окно” **допустимо**: + DQ логирует `NOTICE` и завершает проверку, не падая. +- Для snapshot-справочников (`airports`, `airplanes`, `routes`, `seats`) пустой источник считаем ошибкой: + это почти всегда признак проблем с PXF/источником. ### 3.2. Комментарии в `*_load.sql`: точнее формулировать “идемпотентность”, а не “дедупликацию источника” diff --git a/docs/internal/db_schema.md b/docs/internal/db_schema.md index b96d328..568d159 100644 --- a/docs/internal/db_schema.md +++ b/docs/internal/db_schema.md @@ -101,7 +101,7 @@ - `total_amount TEXT` - общая сумма - **Технические колонки:** `src_created_at_ts` (=book_date), `load_dttm`, `batch_id` - **Стратегия загрузки:** Инкремент по `book_date` -- **DQ проверки:** count (окно инкремента), дубликаты book_ref, NULL обязательных полей +- **DQ проверки:** count (окно инкремента, пустое окно допустимо), дубликаты book_ref, NULL обязательных полей #### stg.tickets (транзакции, инкремент) - **Источник:** `bookings.tickets` (через PXF) @@ -115,7 +115,7 @@ - `outbound TEXT` - направление (в источнике boolean) - **Технические колонки:** `src_created_at_ts` (из book_date через bookings), `load_dttm`, `batch_id` - **Стратегия загрузки:** Инкремент по `book_date` (через bookings) -- **DQ проверки:** count (окно инкремента), дубликаты ticket_no, NULL обязательных полей, пустой passenger_name, ссылочная целостность (bookings) +- **DQ проверки:** count (окно инкремента, пустое окно допустимо), дубликаты ticket_no, NULL обязательных полей, пустой passenger_name, ссылочная целостность (bookings) #### stg.airports (справочник, full load) - **Источник:** `bookings.airports_data` (через PXF) @@ -185,7 +185,7 @@ - `actual_arrival TEXT` - фактическое время прилёта - **Технические колонки:** `src_created_at_ts` (=scheduled_departure), `load_dttm`, `batch_id` - **Стратегия загрузки:** Инкремент по `scheduled_departure` -- **DQ проверки:** count (окно инкремента), дубликаты flight_id, NULL обязательных полей, ссылочная целостность (routes, batch_id = текущий батч) +- **DQ проверки:** count (окно инкремента, пустое окно допустимо), дубликаты flight_id, NULL обязательных полей, ссылочная целостность (routes, batch_id = текущий батч) #### stg.segments (транзакции, инкремент) - **Источник:** `bookings.segments` (через PXF) @@ -198,7 +198,7 @@ - `price TEXT` - цена - **Технические колонки:** `src_created_at_ts` (из book_date через tickets), `load_dttm`, `batch_id` - **Стратегия загрузки:** Инкремент по `book_date` (через tickets) -- **DQ проверки:** count (окно инкремента), дубликаты (ticket_no, flight_id), NULL обязательных полей, ссылочная целостность (tickets, flights) +- **DQ проверки:** count (окно инкремента, пустое окно допустимо), дубликаты (ticket_no, flight_id), NULL обязательных полей, ссылочная целостность (tickets, flights) #### stg.boarding_passes (транзакции, full snapshot) - **Источник:** `bookings.boarding_passes` (через PXF) diff --git a/sql/stg/bookings_dq.sql b/sql/stg/bookings_dq.sql index 24f0e5e..65c8f79 100644 --- a/sql/stg/bookings_dq.sql +++ b/sql/stg/bookings_dq.sql @@ -26,9 +26,25 @@ BEGIN WHERE book_date > COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); IF v_src_count = 0 THEN - RAISE EXCEPTION - 'В источнике bookings_ext нет строк для окна инкремента (book_date > %). Проверьте генерацию данных (make bookings-init / make bookings-generate-day или таск generate_bookings_day).', - COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); + -- Пустое окно инкремента допустимо: новых данных может не быть. + -- В этом случае ожидаем, что в текущем batch_id тоже 0 строк. + SELECT COUNT(*) + INTO v_stg_count + FROM stg.bookings + WHERE batch_id = v_batch_id; + + IF v_stg_count <> 0 THEN + RAISE EXCEPTION + 'DQ FAILED: источник bookings_ext за окно инкремента пустой, но в stg.bookings есть строки текущего batch_id (batch_id=%): %', + v_batch_id, + v_stg_count; + END IF; + + RAISE NOTICE + 'В источнике bookings_ext нет строк для окна инкремента (book_date > %). Пропускаем DQ проверки (batch_id=%).', + COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'), + v_batch_id; + RETURN; END IF; -- Считаем строки, реально вставленные в stg.bookings в этом батче diff --git a/sql/stg/flights_dq.sql b/sql/stg/flights_dq.sql index 09180db..97a0b72 100644 --- a/sql/stg/flights_dq.sql +++ b/sql/stg/flights_dq.sql @@ -24,9 +24,25 @@ BEGIN WHERE scheduled_departure > COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); IF v_src_count = 0 THEN - RAISE EXCEPTION - 'В источнике flights_ext нет строк для окна инкремента (scheduled_departure > %).', - COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); + -- Пустое окно инкремента допустимо: новых данных может не быть. + -- В этом случае ожидаем, что в текущем batch_id тоже 0 строк. + SELECT COUNT(*) + INTO v_stg_count + FROM stg.flights + WHERE batch_id = v_batch_id; + + IF v_stg_count <> 0 THEN + RAISE EXCEPTION + 'DQ FAILED: источник flights_ext за окно инкремента пустой, но в stg.flights есть строки текущего batch_id (batch_id=%): %', + v_batch_id, + v_stg_count; + END IF; + + RAISE NOTICE + 'В источнике flights_ext нет строк для окна инкремента (scheduled_departure > %). Пропускаем DQ проверки (batch_id=%).', + COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'), + v_batch_id; + RETURN; END IF; -- Считаем строки, реально вставленные в stg.flights в этом батче diff --git a/sql/stg/segments_dq.sql b/sql/stg/segments_dq.sql index 3c00e12..d59ddf4 100644 --- a/sql/stg/segments_dq.sql +++ b/sql/stg/segments_dq.sql @@ -27,9 +27,25 @@ BEGIN WHERE b.book_date > COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); IF v_src_count = 0 THEN - RAISE EXCEPTION - 'В источнике segments_ext нет строк для окна инкремента (book_date > %).', - COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); + -- Пустое окно инкремента допустимо: новых данных может не быть. + -- В этом случае ожидаем, что в текущем batch_id тоже 0 строк. + SELECT COUNT(*) + INTO v_stg_count + FROM stg.segments + WHERE batch_id = v_batch_id; + + IF v_stg_count <> 0 THEN + RAISE EXCEPTION + 'DQ FAILED: источник segments_ext за окно инкремента пустой, но в stg.segments есть строки текущего batch_id (batch_id=%): %', + v_batch_id, + v_stg_count; + END IF; + + RAISE NOTICE + 'В источнике segments_ext нет строк для окна инкремента (book_date > %). Пропускаем DQ проверки (batch_id=%).', + COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'), + v_batch_id; + RETURN; END IF; -- Считаем строки, реально вставленные в stg.segments в этом батче diff --git a/sql/stg/tickets_dq.sql b/sql/stg/tickets_dq.sql index e42a412..b4b6951 100644 --- a/sql/stg/tickets_dq.sql +++ b/sql/stg/tickets_dq.sql @@ -26,9 +26,25 @@ BEGIN WHERE b.book_date > COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); IF v_source_count = 0 THEN - RAISE EXCEPTION - 'В источнике tickets_ext нет строк для окна инкремента (book_date > %). Проверьте генерацию данных (таск generate_bookings_day).', - COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'); + -- Пустое окно инкремента допустимо: новых данных может не быть. + -- В этом случае ожидаем, что в текущем batch_id тоже 0 строк. + SELECT COUNT(*) + INTO v_stg_count + FROM stg.tickets + WHERE batch_id = v_batch_id; + + IF v_stg_count <> 0 THEN + RAISE EXCEPTION + 'DQ FAILED: источник tickets_ext за окно инкремента пустой, но в stg.tickets есть строки текущего batch_id (batch_id=%): %', + v_batch_id, + v_stg_count; + END IF; + + RAISE NOTICE + 'В источнике tickets_ext нет строк для окна инкремента (book_date > %). Пропускаем DQ проверки (batch_id=%).', + COALESCE(v_prev_ts, TIMESTAMP '1900-01-01 00:00:00'), + v_batch_id; + RETURN; END IF; -- Количество в STG (текущий батч)