docs(qa-plan): исправлены SQL-запросы по результатам прогона отладки

- Зачем:
  - план содержал неверные имена полей и формулы, которые давали ошибки при выполнении.
- Что:
  - `_batch_id` → `batch_id` в запросах снапшот-таблиц STG (реальное имя колонки).
  - формула SCD2-маршрутов: `COUNT(DISTINCT route_no)` вместо `route_no || '-' || validity`
    (DDS берёт один route_no с последним validity через rn=1).
  - `ticket_price` → `price`, `boarding_seq IS NOT NULL` → `is_boarded = TRUE`
    (реальные колонки dds.fact_flight_sales).
  - Блок 2: добавлено пояснение, что STG-DAG всегда генерирует новый день,
    поэтому тест идемпотентности — только ODS/DDS/DM без повторного запуска STG.
  - Блок 5.2: уточнено, что ODS намеренно использует batch_id из STG вместо run_id
    для сквозного lineage — это паттерн, а не баг.
- Проверка:
  - все SQL-запросы из плана выполнены на стенде без ошибок.
This commit is contained in:
2026-03-09 19:21:32 +03:00
parent c81bdb86d9
commit 0ee2d81383
+33 -13
View File
@@ -136,22 +136,23 @@ UNION ALL SELECT 'boarding_passes',
```sql ```sql
-- Снапшот-таблицы: ODS = последний батч STG -- Снапшот-таблицы: ODS = последний батч STG
-- Примечание: поле называется batch_id (без подчёркивания), а не _batch_id
SELECT SELECT
'airports' AS entity, 'airports' AS entity,
(SELECT COUNT(*) FROM stg.airports (SELECT COUNT(*) FROM stg.airports
WHERE _batch_id = (SELECT MAX(_batch_id) FROM stg.airports)) AS stg_cnt, WHERE batch_id = (SELECT MAX(batch_id) FROM stg.airports)) AS stg_cnt,
(SELECT COUNT(*) FROM ods.airports) AS ods_cnt (SELECT COUNT(*) FROM ods.airports) AS ods_cnt
UNION ALL SELECT 'airplanes', UNION ALL SELECT 'airplanes',
(SELECT COUNT(*) FROM stg.airplanes (SELECT COUNT(*) FROM stg.airplanes
WHERE _batch_id = (SELECT MAX(_batch_id) FROM stg.airplanes)), WHERE batch_id = (SELECT MAX(batch_id) FROM stg.airplanes)),
(SELECT COUNT(*) FROM ods.airplanes) (SELECT COUNT(*) FROM ods.airplanes)
UNION ALL SELECT 'routes', UNION ALL SELECT 'routes',
(SELECT COUNT(*) FROM stg.routes (SELECT COUNT(*) FROM stg.routes
WHERE _batch_id = (SELECT MAX(_batch_id) FROM stg.routes)), WHERE batch_id = (SELECT MAX(batch_id) FROM stg.routes)),
(SELECT COUNT(*) FROM ods.routes) (SELECT COUNT(*) FROM ods.routes)
UNION ALL SELECT 'seats', UNION ALL SELECT 'seats',
(SELECT COUNT(*) FROM stg.seats (SELECT COUNT(*) FROM stg.seats
WHERE _batch_id = (SELECT MAX(_batch_id) FROM stg.seats)), WHERE batch_id = (SELECT MAX(batch_id) FROM stg.seats)),
(SELECT COUNT(*) FROM ods.seats); (SELECT COUNT(*) FROM ods.seats);
``` ```
@@ -180,8 +181,11 @@ UNION ALL SELECT 'passengers',
```sql ```sql
-- Маршруты SCD2: текущих записей = уникальных бизнес-ключей в ODS -- Маршруты SCD2: текущих записей = уникальных бизнес-ключей в ODS
-- Примечание: бизнес-ключ = route_no (не route_no || '-' || validity).
-- Один route_no может иметь несколько периодов validity в ods.routes,
-- но DDS берёт только самый свежий (rn=1 по validity DESC).
SELECT SELECT
(SELECT COUNT(DISTINCT route_no || '-' || validity) FROM ods.routes) AS ods_routes, (SELECT COUNT(DISTINCT route_no) FROM ods.routes) AS ods_routes,
(SELECT COUNT(*) FROM dds.dim_routes WHERE valid_to IS NULL) AS dds_current, (SELECT COUNT(*) FROM dds.dim_routes WHERE valid_to IS NULL) AS dds_current,
(SELECT COUNT(*) FROM dds.dim_routes) AS dds_total; (SELECT COUNT(*) FROM dds.dim_routes) AS dds_total;
``` ```
@@ -201,8 +205,9 @@ SELECT
```sql ```sql
-- Общая выручка: fact vs sales_report -- Общая выручка: fact vs sales_report
-- Примечание: колонка называется price (не ticket_price)
SELECT SELECT
(SELECT SUM(ticket_price) FROM dds.fact_flight_sales) AS fact_revenue, (SELECT SUM(price) FROM dds.fact_flight_sales) AS fact_revenue,
(SELECT SUM(total_revenue) FROM dm.sales_report) AS dm_revenue; (SELECT SUM(total_revenue) FROM dm.sales_report) AS dm_revenue;
``` ```
@@ -219,13 +224,12 @@ SELECT
**Ожидание:** совпадение (или объяснимая разница). **Ожидание:** совпадение (или объяснимая разница).
```sql ```sql
-- Общее число посадок: fact vs airport_traffic -- Общее число посадок: fact vs sales_report
-- Примечание: airport_traffic считает и вылеты и прилёты отдельно, -- Примечание: колонка называется is_boarded (не boarding_seq)
-- поэтому dm_total_pax может быть ~2x от fact_boarded
SELECT SELECT
(SELECT COUNT(*) FROM dds.fact_flight_sales (SELECT COUNT(*) FROM dds.fact_flight_sales
WHERE boarding_seq IS NOT NULL) AS fact_boarded, WHERE is_boarded = TRUE) AS fact_boarded,
(SELECT SUM(total_passengers) FROM dm.airport_traffic) AS dm_total_pax; (SELECT SUM(passengers_boarded) FROM dm.sales_report) AS dm_boarded;
``` ```
#### E. Целостность суррогатных ключей (FK в DDS и DM) #### E. Целостность суррогатных ключей (FK в DDS и DM)
@@ -267,7 +271,19 @@ WHERE f.calendar_sk IS NOT NULL AND c.calendar_sk IS NULL;
## Блок 2: Идемпотентность (повторный Day 1) ## Блок 2: Идемпотентность (повторный Day 1)
**Цель:** повторный прогон тех же DAG'ов НЕ дублирует данные. **Цель:** повторный прогон ODS/DDS/DM НЕ дублирует данные при неизменённом STG.
**Важно:** `bookings_to_gp_stage` ВСЕГДА генерирует следующий день при запуске
(через `generate_bookings_day`). Тест идемпотентности нужно проводить только для
`bookings_to_gp_ods`, `bookings_to_gp_dds`, `bookings_to_gp_dm` — без повторного
запуска STG. Убедитесь, что все STG-батчи уже обработаны ODS перед тестом:
```sql
SELECT COUNT(*) AS unprocessed
FROM stg.bookings
WHERE load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01') FROM ods.bookings);
-- Ожидание: 0
```
### 2.1 Зафиксировать counts после Day 1 ### 2.1 Зафиксировать counts после Day 1
@@ -484,8 +500,12 @@ grep -r '_batch_id' sql/dds/*_load.sql sql/dm/*_load.sql # ожидание: п
### 5.2 Все load.sql используют шаблон {{ run_id }} ### 5.2 Все load.sql используют шаблон {{ run_id }}
Примечание: ODS намеренно не использует `{{ run_id }}` — вместо этого в `_load_id`
сохраняется `batch_id` из STG для сквозного lineage (traceable to source batch).
Это правильный паттерн, а не баг. Проверять нужно только STG/DDS/DM.
```bash ```bash
for f in sql/stg/*_load.sql sql/ods/*_load.sql sql/dds/*_load.sql sql/dm/*_load.sql; do for f in sql/stg/*_load.sql sql/dds/*_load.sql sql/dm/*_load.sql; do
if ! grep -q '{{ run_id }}\|{{ ti.xcom_pull' "$f"; then if ! grep -q '{{ run_id }}\|{{ ti.xcom_pull' "$f"; then
echo "WARN: $f не содержит {{ run_id }}" echo "WARN: $f не содержит {{ run_id }}"
fi fi