4779 lines
155 KiB
JSON
4779 lines
155 KiB
JSON
{
|
||
"title": "Хранилище авиаперевозок",
|
||
"lead": "От бронирования до витрины: выберите таблицу или связь.",
|
||
"repository_url": "https://git.dementev.space/ddmitry/airflow-greenplum/src/",
|
||
"layers": [
|
||
{
|
||
"id": "source",
|
||
"title": "Источник",
|
||
"caption": "Postgres · demo"
|
||
},
|
||
{
|
||
"id": "stg",
|
||
"title": "STG",
|
||
"caption": "Сырые строки · Greenplum"
|
||
},
|
||
{
|
||
"id": "ods",
|
||
"title": "ODS",
|
||
"caption": "Типы и текущее состояние"
|
||
},
|
||
{
|
||
"id": "dds",
|
||
"title": "DDS",
|
||
"caption": "Измерения и факт"
|
||
},
|
||
{
|
||
"id": "dm",
|
||
"title": "DM",
|
||
"caption": "Ответы на бизнес-вопросы"
|
||
}
|
||
],
|
||
"nodes": [
|
||
{
|
||
"id": "bookings.bookings",
|
||
"name": "bookings.bookings",
|
||
"layer": "source",
|
||
"order": 0,
|
||
"what": "Бронирование объединяет билеты одной покупки.",
|
||
"grain": "Одно бронирование.",
|
||
"keys": "`book_ref` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "номер бронирования"
|
||
},
|
||
{
|
||
"name": "book_date",
|
||
"meaning": "момент бронирования"
|
||
},
|
||
{
|
||
"name": "total_amount",
|
||
"meaning": "сумма бронирования целиком"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.bookings_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/bookings_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.bookings",
|
||
"name": "stg.bookings",
|
||
"layer": "stg",
|
||
"order": 0,
|
||
"what": "Бронирование объединяет билеты одной покупки. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Одно бронирование. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `book_ref`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "номер бронирования"
|
||
},
|
||
{
|
||
"name": "book_date",
|
||
"meaning": "момент бронирования"
|
||
},
|
||
{
|
||
"name": "total_amount",
|
||
"meaning": "сумма бронирования целиком"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "опорное время инкремента"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/bookings_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/bookings_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/bookings_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.bookings_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.bookings",
|
||
"name": "ods.bookings",
|
||
"layer": "ods",
|
||
"order": 0,
|
||
"what": "Бронирование объединяет билеты одной покупки.",
|
||
"grain": "Одно бронирование.",
|
||
"keys": "`book_ref` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "номер бронирования"
|
||
},
|
||
{
|
||
"name": "book_date",
|
||
"meaning": "момент бронирования"
|
||
},
|
||
{
|
||
"name": "total_amount",
|
||
"meaning": "сумма бронирования целиком"
|
||
}
|
||
],
|
||
"fill": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/bookings_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/bookings_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/bookings_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.tickets",
|
||
"name": "bookings.tickets",
|
||
"layer": "source",
|
||
"order": 1,
|
||
"what": "Билет связывает пассажира с бронированием. В билете может быть несколько сегментов.",
|
||
"grain": "Один билет пассажира.",
|
||
"keys": "`ticket_no` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "номер билета"
|
||
},
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "бронирование"
|
||
},
|
||
{
|
||
"name": "passenger_id",
|
||
"meaning": "идентификатор пассажира"
|
||
},
|
||
{
|
||
"name": "passenger_name",
|
||
"meaning": "имя пассажира"
|
||
},
|
||
{
|
||
"name": "outbound",
|
||
"meaning": "признак направления поездки"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.tickets_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/tickets_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.tickets",
|
||
"name": "stg.tickets",
|
||
"layer": "stg",
|
||
"order": 1,
|
||
"what": "Билет связывает пассажира с бронированием. В билете может быть несколько сегментов. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один билет пассажира. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `ticket_no`. Загрузчик исключает повторный `ticket_no` во всей таблице.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "номер билета"
|
||
},
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "бронирование"
|
||
},
|
||
{
|
||
"name": "passenger_id",
|
||
"meaning": "идентификатор пассажира"
|
||
},
|
||
{
|
||
"name": "passenger_name",
|
||
"meaning": "имя пассажира"
|
||
},
|
||
{
|
||
"name": "outbound",
|
||
"meaning": "признак направления поездки"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "опорное время инкремента"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Уже сохраненный `ticket_no` повторно не вставляется.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/tickets_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/tickets_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/tickets_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.tickets_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.tickets",
|
||
"name": "ods.tickets",
|
||
"layer": "ods",
|
||
"order": 1,
|
||
"what": "Билет связывает пассажира с бронированием. В билете может быть несколько сегментов.",
|
||
"grain": "Один билет пассажира.",
|
||
"keys": "`ticket_no` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "номер билета"
|
||
},
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "бронирование"
|
||
},
|
||
{
|
||
"name": "passenger_id",
|
||
"meaning": "идентификатор пассажира"
|
||
},
|
||
{
|
||
"name": "passenger_name",
|
||
"meaning": "имя пассажира"
|
||
},
|
||
{
|
||
"name": "is_outbound",
|
||
"meaning": "признак направления поездки"
|
||
}
|
||
],
|
||
"fill": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. `outbound` становится `is_outbound` (BOOLEAN).",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/tickets_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/tickets_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/tickets_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.segments",
|
||
"name": "bookings.segments",
|
||
"layer": "source",
|
||
"order": 2,
|
||
"what": "Сегмент связывает билет с конкретным рейсом и хранит цену этого перелета.",
|
||
"grain": "Один перелет по одному билету.",
|
||
"keys": "`ticket_no` + `flight_id` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания"
|
||
},
|
||
{
|
||
"name": "price",
|
||
"meaning": "стоимость сегмента"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.segments_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/segments_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.segments",
|
||
"name": "stg.segments",
|
||
"layer": "stg",
|
||
"order": 2,
|
||
"what": "Сегмент связывает билет с конкретным рейсом и хранит цену этого перелета. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один перелет по одному билету. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `ticket_no` + `flight_id`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания"
|
||
},
|
||
{
|
||
"name": "price",
|
||
"meaning": "стоимость сегмента"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "опорное время инкремента"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/segments_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/segments_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/segments_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.segments_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.segments",
|
||
"name": "ods.segments",
|
||
"layer": "ods",
|
||
"order": 2,
|
||
"what": "Сегмент связывает билет с конкретным рейсом и хранит цену этого перелета.",
|
||
"grain": "Один перелет по одному билету.",
|
||
"keys": "`ticket_no` + `flight_id` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания"
|
||
},
|
||
{
|
||
"name": "amount",
|
||
"meaning": "стоимость сегмента"
|
||
}
|
||
],
|
||
"fill": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. `stg.segments.price` становится `amount` (NUMERIC).",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/segments_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/segments_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/segments_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.flights",
|
||
"name": "bookings.flights",
|
||
"layer": "source",
|
||
"order": 3,
|
||
"what": "Рейс представляет конкретный вылет по маршруту. Один маршрут выполняется многократно.",
|
||
"grain": "Один рейс с собственным расписанием и статусом.",
|
||
"keys": "`flight_id` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "идентификатор рейса"
|
||
},
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "маршрут"
|
||
},
|
||
{
|
||
"name": "scheduled_departure",
|
||
"meaning": "плановый момент вылета"
|
||
},
|
||
{
|
||
"name": "scheduled_arrival",
|
||
"meaning": "плановый момент прилета"
|
||
},
|
||
{
|
||
"name": "status",
|
||
"meaning": "состояние рейса"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.flights_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/flights_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.flights",
|
||
"name": "stg.flights",
|
||
"layer": "stg",
|
||
"order": 3,
|
||
"what": "Рейс представляет конкретный вылет по маршруту. Один маршрут выполняется многократно. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один рейс с собственным расписанием и статусом. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `flight_id`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "идентификатор рейса"
|
||
},
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "маршрут"
|
||
},
|
||
{
|
||
"name": "scheduled_departure",
|
||
"meaning": "плановый момент вылета"
|
||
},
|
||
{
|
||
"name": "scheduled_arrival",
|
||
"meaning": "плановый момент прилета"
|
||
},
|
||
{
|
||
"name": "status",
|
||
"meaning": "состояние рейса"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "опорное время инкремента"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `scheduled_departure`. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/flights_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/flights_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/flights_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.flights_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.flights",
|
||
"name": "ods.flights",
|
||
"layer": "ods",
|
||
"order": 3,
|
||
"what": "Рейс представляет конкретный вылет по маршруту. Один маршрут выполняется многократно.",
|
||
"grain": "Один рейс с собственным расписанием и статусом.",
|
||
"keys": "`flight_id` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "идентификатор рейса"
|
||
},
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "маршрут"
|
||
},
|
||
{
|
||
"name": "scheduled_departure",
|
||
"meaning": "плановый момент вылета"
|
||
},
|
||
{
|
||
"name": "scheduled_arrival",
|
||
"meaning": "плановый момент прилета"
|
||
},
|
||
{
|
||
"name": "status",
|
||
"meaning": "состояние рейса"
|
||
}
|
||
],
|
||
"fill": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. Дополнительно берутся рейсы из истории STG, на которые ссылаются новые `stg.segments`.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/flights_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/flights_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/flights_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.boarding_passes",
|
||
"name": "bookings.boarding_passes",
|
||
"layer": "source",
|
||
"order": 4,
|
||
"what": "Посадочный талон относится к сегменту билета. Его наличие используется как признак посадки в факте.",
|
||
"grain": "Один посадочный талон на билет и рейс.",
|
||
"keys": "`ticket_no` + `flight_id` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "место в салоне"
|
||
},
|
||
{
|
||
"name": "boarding_no",
|
||
"meaning": "номер посадочного талона"
|
||
},
|
||
{
|
||
"name": "boarding_time",
|
||
"meaning": "время посадки"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.boarding_passes_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/boarding_passes_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.boarding_passes",
|
||
"name": "stg.boarding_passes",
|
||
"layer": "stg",
|
||
"order": 4,
|
||
"what": "Посадочный талон относится к сегменту билета. Его наличие используется как признак посадки в факте. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один посадочный талон на билет и рейс. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `ticket_no` + `flight_id`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "место в салоне"
|
||
},
|
||
{
|
||
"name": "boarding_no",
|
||
"meaning": "номер посадочного талона"
|
||
},
|
||
{
|
||
"name": "boarding_time",
|
||
"meaning": "время посадки"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "опорное время инкремента"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/boarding_passes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/boarding_passes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/boarding_passes_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.boarding_passes_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.boarding_passes",
|
||
"name": "ods.boarding_passes",
|
||
"layer": "ods",
|
||
"order": 4,
|
||
"what": "Посадочный талон относится к сегменту билета. Его наличие используется как признак посадки в факте.",
|
||
"grain": "Один посадочный талон на билет и рейс.",
|
||
"keys": "`ticket_no` + `flight_id` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "место в салоне"
|
||
},
|
||
{
|
||
"name": "boarding_no",
|
||
"meaning": "номер посадочного талона"
|
||
},
|
||
{
|
||
"name": "boarding_time",
|
||
"meaning": "время посадки"
|
||
}
|
||
],
|
||
"fill": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/boarding_passes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/boarding_passes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/boarding_passes_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.routes",
|
||
"name": "bookings.routes",
|
||
"layer": "source",
|
||
"order": 5,
|
||
"what": "Маршрут задает аэропорты, модель самолета и расписание на период действия.",
|
||
"grain": "Один вариант расписания маршрута.",
|
||
"keys": "`route_no` + `validity` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "номер маршрута"
|
||
},
|
||
{
|
||
"name": "validity",
|
||
"meaning": "период действия исходного расписания"
|
||
},
|
||
{
|
||
"name": "departure_airport",
|
||
"meaning": "код аэропорта вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport",
|
||
"meaning": "код аэропорта прилета"
|
||
},
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели самолета"
|
||
},
|
||
{
|
||
"name": "scheduled_time",
|
||
"meaning": "время вылета по расписанию"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.routes_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/routes_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.routes",
|
||
"name": "stg.routes",
|
||
"layer": "stg",
|
||
"order": 5,
|
||
"what": "Маршрут задает аэропорты, модель самолета и расписание на период действия. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один вариант расписания маршрута. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `route_no` + `validity`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "номер маршрута"
|
||
},
|
||
{
|
||
"name": "validity",
|
||
"meaning": "период действия исходного расписания"
|
||
},
|
||
{
|
||
"name": "departure_airport",
|
||
"meaning": "код аэропорта вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport",
|
||
"meaning": "код аэропорта прилета"
|
||
},
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели самолета"
|
||
},
|
||
{
|
||
"name": "scheduled_time",
|
||
"meaning": "время вылета по расписанию"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "время снимка (`now()`)"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/routes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/routes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/routes_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.routes_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.routes",
|
||
"name": "ods.routes",
|
||
"layer": "ods",
|
||
"order": 5,
|
||
"what": "Маршрут задает аэропорты, модель самолета и расписание на период действия.",
|
||
"grain": "Один вариант расписания маршрута.",
|
||
"keys": "`route_no` + `validity` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "route_no",
|
||
"meaning": "номер маршрута"
|
||
},
|
||
{
|
||
"name": "validity",
|
||
"meaning": "период действия исходного расписания"
|
||
},
|
||
{
|
||
"name": "departure_airport",
|
||
"meaning": "код аэропорта вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport",
|
||
"meaning": "код аэропорта прилета"
|
||
},
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели самолета"
|
||
},
|
||
{
|
||
"name": "departure_time",
|
||
"meaning": "время вылета по расписанию"
|
||
}
|
||
],
|
||
"fill": "Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ. `scheduled_time` становится `departure_time` (TIME), `days_of_week` — INTEGER[], `duration` — INTERVAL.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/routes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/routes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/routes_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.airports_data",
|
||
"name": "bookings.airports_data",
|
||
"layer": "source",
|
||
"order": 6,
|
||
"what": "Справочник аэропортов. Один аэропорт может участвовать в разных рейсах как место вылета или прилета.",
|
||
"grain": "Один аэропорт.",
|
||
"keys": "`airport_code` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "airport_code",
|
||
"meaning": "код аэропорта"
|
||
},
|
||
{
|
||
"name": "airport_name",
|
||
"meaning": "название"
|
||
},
|
||
{
|
||
"name": "city",
|
||
"meaning": "город"
|
||
},
|
||
{
|
||
"name": "timezone",
|
||
"meaning": "часовой пояс"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.airports_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/airports_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.airports",
|
||
"name": "stg.airports",
|
||
"layer": "stg",
|
||
"order": 6,
|
||
"what": "Справочник аэропортов. Один аэропорт может участвовать в разных рейсах как место вылета или прилета. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Один аэропорт. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `airport_code`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "airport_code",
|
||
"meaning": "код аэропорта"
|
||
},
|
||
{
|
||
"name": "airport_name",
|
||
"meaning": "название"
|
||
},
|
||
{
|
||
"name": "city",
|
||
"meaning": "город"
|
||
},
|
||
{
|
||
"name": "timezone",
|
||
"meaning": "часовой пояс"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "время снимка (`now()`)"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/airports_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/airports_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/airports_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.airports_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.airports",
|
||
"name": "ods.airports",
|
||
"layer": "ods",
|
||
"order": 6,
|
||
"what": "Справочник аэропортов. Один аэропорт может участвовать в разных рейсах как место вылета или прилета.",
|
||
"grain": "Один аэропорт.",
|
||
"keys": "`airport_code` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "airport_code",
|
||
"meaning": "код аэропорта"
|
||
},
|
||
{
|
||
"name": "airport_name",
|
||
"meaning": "название"
|
||
},
|
||
{
|
||
"name": "city",
|
||
"meaning": "город"
|
||
},
|
||
{
|
||
"name": "timezone",
|
||
"meaning": "часовой пояс"
|
||
}
|
||
],
|
||
"fill": "Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ. Из JSON извлекаются русские названия аэропорта, города и страны.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/airports_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/ods/airports_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/ods/airports_dq.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#ods-airports"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.airplanes_data",
|
||
"name": "bookings.airplanes_data",
|
||
"layer": "source",
|
||
"order": 7,
|
||
"what": "Справочник моделей самолетов; бортовых номеров отдельных самолетов здесь нет.",
|
||
"grain": "Одна модель самолета.",
|
||
"keys": "`airplane_code` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели"
|
||
},
|
||
{
|
||
"name": "model",
|
||
"meaning": "название модели"
|
||
},
|
||
{
|
||
"name": "range",
|
||
"meaning": "дальность полета"
|
||
},
|
||
{
|
||
"name": "speed",
|
||
"meaning": "скорость"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.airplanes_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/airplanes_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.airplanes",
|
||
"name": "stg.airplanes",
|
||
"layer": "stg",
|
||
"order": 7,
|
||
"what": "Справочник моделей самолетов; бортовых номеров отдельных самолетов здесь нет. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Одна модель самолета. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `airplane_code`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели"
|
||
},
|
||
{
|
||
"name": "model",
|
||
"meaning": "название модели"
|
||
},
|
||
{
|
||
"name": "range",
|
||
"meaning": "дальность полета"
|
||
},
|
||
{
|
||
"name": "speed",
|
||
"meaning": "скорость"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "время снимка (`now()`)"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/airplanes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/airplanes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/airplanes_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.airplanes_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.airplanes",
|
||
"name": "ods.airplanes",
|
||
"layer": "ods",
|
||
"order": 7,
|
||
"what": "Справочник моделей самолетов; бортовых номеров отдельных самолетов здесь нет.",
|
||
"grain": "Одна модель самолета.",
|
||
"keys": "`airplane_code` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "код модели"
|
||
},
|
||
{
|
||
"name": "model",
|
||
"meaning": "название модели"
|
||
},
|
||
{
|
||
"name": "range_km",
|
||
"meaning": "дальность полета"
|
||
},
|
||
{
|
||
"name": "speed_kmh",
|
||
"meaning": "скорость"
|
||
}
|
||
],
|
||
"fill": "Ожидается по ТЗ: Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/airplanes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/ods/airplanes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/ods/airplanes_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание 1.1",
|
||
"url": "../assignment/analyst_spec.md#11-odsairplanes"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "bookings.seats",
|
||
"name": "bookings.seats",
|
||
"layer": "source",
|
||
"order": 8,
|
||
"what": "Схема салона модели самолета. Номер места повторяется у разных моделей.",
|
||
"grain": "Одно место в салоне модели.",
|
||
"keys": "`airplane_code` + `seat_no` — логический ключ строки источника.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "модель самолета"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "номер места"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания места"
|
||
}
|
||
],
|
||
"fill": "Демо-БД восстанавливается из seed-дампа; генератор добавляет учебные дни. PXF читает ее через `stg.seats_ext`.",
|
||
"status": "source",
|
||
"links": [
|
||
{
|
||
"label": "Источник и генератор",
|
||
"url": "../../bookings/README.md"
|
||
},
|
||
{
|
||
"label": "DDL входа PXF",
|
||
"url": "../../sql/stg/seats_ddl.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "stg.seats",
|
||
"name": "stg.seats",
|
||
"layer": "stg",
|
||
"order": 8,
|
||
"what": "Схема салона модели самолета. Номер места повторяется у разных моделей. Здесь бизнес-поля хранятся как TEXT.",
|
||
"grain": "Одно место в салоне модели. Строка относится к загрузке STG.",
|
||
"keys": "Бизнес-ключ: `airplane_code` + `seat_no`. Для хранимой строки учитывайте также `_load_id`: снимки разных запусков сосуществуют.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "модель самолета"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "номер места"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания места"
|
||
},
|
||
{
|
||
"name": "event_ts",
|
||
"meaning": "время снимка (`now()`)"
|
||
},
|
||
{
|
||
"name": "_load_id",
|
||
"meaning": "идентификатор загрузки"
|
||
},
|
||
{
|
||
"name": "_load_ts",
|
||
"meaning": "время записи в STG"
|
||
}
|
||
],
|
||
"fill": "Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/stg/seats_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/stg/seats_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/stg/seats_dq.sql"
|
||
}
|
||
],
|
||
"parts": [
|
||
"stg.seats_ext"
|
||
]
|
||
},
|
||
{
|
||
"id": "ods.seats",
|
||
"name": "ods.seats",
|
||
"layer": "ods",
|
||
"order": 8,
|
||
"what": "Схема салона модели самолета. Номер места повторяется у разных моделей.",
|
||
"grain": "Одно место в салоне модели.",
|
||
"keys": "`airplane_code` + `seat_no` — логический ключ текущей строки.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "модель самолета"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "номер места"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "класс обслуживания места"
|
||
}
|
||
],
|
||
"fill": "Ожидается по ТЗ: Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/ods/seats_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/ods/seats_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/ods/seats_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание 1.2",
|
||
"url": "../assignment/analyst_spec.md#12-odsseats"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_calendar",
|
||
"name": "dds.dim_calendar",
|
||
"layer": "dds",
|
||
"order": 0,
|
||
"what": "Дает дату и календарные признаки рейса.",
|
||
"grain": "Одна календарная дата.",
|
||
"keys": "`date_actual` — дата; `calendar_sk` — ключ измерения.",
|
||
"fields": [
|
||
{
|
||
"name": "calendar_sk",
|
||
"meaning": "ключ даты"
|
||
},
|
||
{
|
||
"name": "date_actual",
|
||
"meaning": "дата"
|
||
},
|
||
{
|
||
"name": "year_actual",
|
||
"meaning": "год"
|
||
},
|
||
{
|
||
"name": "month_actual",
|
||
"meaning": "месяц"
|
||
},
|
||
{
|
||
"name": "day_of_week",
|
||
"meaning": "день недели ISO"
|
||
},
|
||
{
|
||
"name": "is_weekend",
|
||
"meaning": "суббота или воскресенье"
|
||
}
|
||
],
|
||
"fill": "Генерация дат 2016–2030 только при пустой таблице. Календарь не читается из ODS.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_calendar_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/dds/dim_calendar_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/dds/dim_calendar_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_tariffs",
|
||
"name": "dds.dim_tariffs",
|
||
"layer": "dds",
|
||
"order": 1,
|
||
"what": "Выносит классы обслуживания из сегментов в измерение.",
|
||
"grain": "Один класс обслуживания.",
|
||
"keys": "`fare_conditions` — бизнес-ключ; `tariff_sk` — ключ измерения.",
|
||
"fields": [
|
||
{
|
||
"name": "tariff_sk",
|
||
"meaning": "ключ класса"
|
||
},
|
||
{
|
||
"name": "fare_conditions",
|
||
"meaning": "Economy, Business или Comfort"
|
||
}
|
||
],
|
||
"fill": "DISTINCT непустых `ods.segments.fare_conditions`; вставляются только новые значения с новым SK.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_tariffs_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/dds/dim_tariffs_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/dds/dim_tariffs_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_passengers",
|
||
"name": "dds.dim_passengers",
|
||
"layer": "dds",
|
||
"order": 2,
|
||
"what": "Собирает пассажиров из билетов: отдельного справочника пассажиров в источнике нет.",
|
||
"grain": "Один пассажир, даже если у него много билетов.",
|
||
"keys": "`passenger_id` — бизнес-ключ; `passenger_sk` — ключ измерения.",
|
||
"fields": [
|
||
{
|
||
"name": "passenger_sk",
|
||
"meaning": "ключ пассажира"
|
||
},
|
||
{
|
||
"name": "passenger_id",
|
||
"meaning": "идентификатор из билетов"
|
||
},
|
||
{
|
||
"name": "passenger_name",
|
||
"meaning": "актуальное имя"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: SCD1; выбрать актуальное имя из билетов, добавлять новых пассажиров и обновлять изменившиеся имена.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_passengers_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dds/dim_passengers_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dds/dim_passengers_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#22-ddsdim_passengers-scd1"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_routes",
|
||
"name": "dds.dim_routes",
|
||
"layer": "dds",
|
||
"order": 3,
|
||
"what": "Хранит версии маршрута для выбора состояния на дату рейса.",
|
||
"grain": "Одна версия маршрута на интервале `[valid_from, valid_to)`.",
|
||
"keys": "`route_bk` повторяется в истории; `route_sk` обозначает одну версию. `valid_to IS NULL` — открытая версия.",
|
||
"fields": [
|
||
{
|
||
"name": "route_sk",
|
||
"meaning": "ключ версии"
|
||
},
|
||
{
|
||
"name": "route_bk",
|
||
"meaning": "номер маршрута"
|
||
},
|
||
{
|
||
"name": "airplane_code",
|
||
"meaning": "модель самолета этой версии"
|
||
},
|
||
{
|
||
"name": "departure_airport",
|
||
"meaning": "код вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport",
|
||
"meaning": "код прилета"
|
||
},
|
||
{
|
||
"name": "total_seats",
|
||
"meaning": "число мест"
|
||
},
|
||
{
|
||
"name": "valid_from",
|
||
"meaning": "начало версии"
|
||
},
|
||
{
|
||
"name": "valid_to",
|
||
"meaning": "конец версии, не включается"
|
||
},
|
||
{
|
||
"name": "hashdiff",
|
||
"meaning": "признак изменения атрибутов"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: SCD2 для атрибутов маршрута; города, модель и число мест обогащаются из измерений. Их обновление у открытых версий само по себе новую версию не создает.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_routes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dds/dim_routes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dds/dim_routes_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#23-ddsdim_routes-scd2"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.fact_flight_sales",
|
||
"name": "dds.fact_flight_sales",
|
||
"layer": "dds",
|
||
"order": 4,
|
||
"what": "Связывает проданный сегмент с измерениями; служит основой витрин.",
|
||
"grain": "Один сегмент билета: `(ticket_no, flight_id)`. Один билет с пересадкой дает несколько строк.",
|
||
"keys": "`ticket_no` + `flight_id` — логическое зерно. `book_ref` повторяется у разных билетов и сегментов. Собственного `fact_sk` нет.",
|
||
"fields": [
|
||
{
|
||
"name": "ticket_no",
|
||
"meaning": "билет"
|
||
},
|
||
{
|
||
"name": "flight_id",
|
||
"meaning": "рейс"
|
||
},
|
||
{
|
||
"name": "book_ref",
|
||
"meaning": "бронирование"
|
||
},
|
||
{
|
||
"name": "book_date",
|
||
"meaning": "дата бронирования"
|
||
},
|
||
{
|
||
"name": "price",
|
||
"meaning": "цена сегмента из `ods.segments.amount`"
|
||
},
|
||
{
|
||
"name": "is_boarded",
|
||
"meaning": "есть ли посадочный талон"
|
||
},
|
||
{
|
||
"name": "seat_no",
|
||
"meaning": "место из талона"
|
||
},
|
||
{
|
||
"name": "calendar_sk",
|
||
"meaning": "дата вылета"
|
||
},
|
||
{
|
||
"name": "departure_airport_sk",
|
||
"meaning": "аэропорт вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport_sk",
|
||
"meaning": "аэропорт прилета"
|
||
},
|
||
{
|
||
"name": "route_sk",
|
||
"meaning": "версия маршрута на дату вылета"
|
||
},
|
||
{
|
||
"name": "airplane_sk",
|
||
"meaning": "модель через выбранную версию маршрута"
|
||
},
|
||
{
|
||
"name": "passenger_sk",
|
||
"meaning": "пассажир"
|
||
},
|
||
{
|
||
"name": "tariff_sk",
|
||
"meaning": "класс обслуживания"
|
||
}
|
||
],
|
||
"fill": "INSERT новых пар `(ticket_no, flight_id)`; UPDATE меняет только `seat_no`, `price`, `is_boarded` и метаданные загрузки. Ключи измерений фиксируются при вставке. До выполнения заданий `passenger_sk`, `route_sk`, `airplane_sk` могут быть NULL. Повторный запуск их не заполнит: после реализации измерений нужен пересчет факта по ТЗ.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/fact_flight_sales_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/dds/fact_flight_sales_dq.sql"
|
||
},
|
||
{
|
||
"label": "Пересчет после реализации измерений",
|
||
"url": "../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#fact-flight-sales"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_airports",
|
||
"name": "dds.dim_airports",
|
||
"layer": "dds",
|
||
"order": 6,
|
||
"what": "Один справочник для двух ролей аэропорта в факте: вылет и прилет.",
|
||
"grain": "Один аэропорт в текущем состоянии.",
|
||
"keys": "`airport_bk` — код из `ods.airports.airport_code`; `airport_sk` — ключ измерения.",
|
||
"fields": [
|
||
{
|
||
"name": "airport_sk",
|
||
"meaning": "ключ аэропорта"
|
||
},
|
||
{
|
||
"name": "airport_bk",
|
||
"meaning": "код аэропорта"
|
||
},
|
||
{
|
||
"name": "city",
|
||
"meaning": "город"
|
||
},
|
||
{
|
||
"name": "airport_name",
|
||
"meaning": "название"
|
||
},
|
||
{
|
||
"name": "timezone",
|
||
"meaning": "часовой пояс"
|
||
}
|
||
],
|
||
"fill": "SCD1 из `ods.airports`: UPDATE изменившихся атрибутов, INSERT новых аэропортов с новым SK.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_airports_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/dds/dim_airports_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/dds/dim_airports_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dds.dim_airplanes",
|
||
"name": "dds.dim_airplanes",
|
||
"layer": "dds",
|
||
"order": 7,
|
||
"what": "Описывает модель самолета и вместимость ее салона.",
|
||
"grain": "Одна модель самолета.",
|
||
"keys": "`airplane_bk` — код модели; `airplane_sk` — ключ измерения.",
|
||
"fields": [
|
||
{
|
||
"name": "airplane_sk",
|
||
"meaning": "ключ модели"
|
||
},
|
||
{
|
||
"name": "airplane_bk",
|
||
"meaning": "код модели"
|
||
},
|
||
{
|
||
"name": "model",
|
||
"meaning": "название"
|
||
},
|
||
{
|
||
"name": "total_seats",
|
||
"meaning": "количество мест из `ods.seats`"
|
||
},
|
||
{
|
||
"name": "range_km",
|
||
"meaning": "дальность, км"
|
||
},
|
||
{
|
||
"name": "speed_kmh",
|
||
"meaning": "скорость, км/ч"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: SCD1 из `ods.airplanes` с подсчетом мест из `ods.seats` по коду модели. Сейчас загрузка и DQ — заглушки.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dds/dim_airplanes_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dds/dim_airplanes_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dds/dim_airplanes_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#21-ddsdim_airplanes-scd1"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dm.sales_report",
|
||
"name": "dm.sales_report",
|
||
"layer": "dm",
|
||
"order": 0,
|
||
"what": "Показывает выручку и долю посадок по направлениям и классам обслуживания.",
|
||
"grain": "Один день вылета × аэропорт вылета × аэропорт прилета × класс обслуживания.",
|
||
"keys": "`flight_date` + `departure_airport_sk` + `arrival_airport_sk` + `tariff_sk` — логический ключ агрегата.",
|
||
"fields": [
|
||
{
|
||
"name": "flight_date",
|
||
"meaning": "дата вылета"
|
||
},
|
||
{
|
||
"name": "departure_airport_sk",
|
||
"meaning": "аэропорт вылета"
|
||
},
|
||
{
|
||
"name": "arrival_airport_sk",
|
||
"meaning": "аэропорт прилета"
|
||
},
|
||
{
|
||
"name": "tariff_sk",
|
||
"meaning": "класс обслуживания"
|
||
},
|
||
{
|
||
"name": "tickets_sold",
|
||
"meaning": "число сегментов, COUNT(*) факта"
|
||
},
|
||
{
|
||
"name": "total_revenue",
|
||
"meaning": "сумма цен сегментов"
|
||
},
|
||
{
|
||
"name": "passengers_boarded",
|
||
"meaning": "сегменты с посадочным талоном"
|
||
},
|
||
{
|
||
"name": "boarding_rate",
|
||
"meaning": "доля сегментов с талоном"
|
||
}
|
||
],
|
||
"fill": "По HWM факта выбираются затронутые даты; за эти даты агрегируются все факты, выполняются UPDATE и INSERT.",
|
||
"status": "ready",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dm/sales_report_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка",
|
||
"url": "../../sql/dm/sales_report_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ",
|
||
"url": "../../sql/dm/sales_report_dq.sql"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dm.airport_traffic",
|
||
"name": "dm.airport_traffic",
|
||
"layer": "dm",
|
||
"order": 2,
|
||
"what": "Сводит вылеты и прилеты каждого аэропорта за день.",
|
||
"grain": "Один аэропорт за день.",
|
||
"keys": "`traffic_date` + `airport_sk` — логический ключ агрегата.",
|
||
"fields": [
|
||
{
|
||
"name": "traffic_date",
|
||
"meaning": "дата из календаря вылета"
|
||
},
|
||
{
|
||
"name": "airport_sk",
|
||
"meaning": "аэропорт в роли вылета или прилета"
|
||
},
|
||
{
|
||
"name": "total_passengers",
|
||
"meaning": "посадки по обеим ролям"
|
||
},
|
||
{
|
||
"name": "departures_revenue",
|
||
"meaning": "выручка вылетов"
|
||
},
|
||
{
|
||
"name": "arrivals_revenue",
|
||
"meaning": "выручка прилетов"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: пересчитывать затронутые дни. Каждый факт участвует в двух ролях. Выручку вылетов и прилетов нельзя складывать: получится двойной счет. Дата обеих ролей берется из календаря факта (дата вылета).",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dm/airport_traffic_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dm/airport_traffic_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dm/airport_traffic_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#31-dmairport_traffic"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dm.route_performance",
|
||
"name": "dm.route_performance",
|
||
"layer": "dm",
|
||
"order": 4,
|
||
"what": "Сравнивает маршруты по выручке, посадкам и заполняемости.",
|
||
"grain": "Один маршрут за всю историю, объединяя его версии.",
|
||
"keys": "`route_bk` — логический ключ агрегата.",
|
||
"fields": [
|
||
{
|
||
"name": "route_bk",
|
||
"meaning": "маршрут независимо от версии"
|
||
},
|
||
{
|
||
"name": "route_sk",
|
||
"meaning": "ключ текущей версии"
|
||
},
|
||
{
|
||
"name": "total_flights",
|
||
"meaning": "уникальные рейсы"
|
||
},
|
||
{
|
||
"name": "total_tickets",
|
||
"meaning": "проданные сегменты"
|
||
},
|
||
{
|
||
"name": "avg_load_factor",
|
||
"meaning": "посадки относительно числа рейсов и мест текущей версии"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: полный пересчет; факты всех версий группируются по бизнес-ключу маршрута, описания берутся из открытой версии.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dm/route_performance_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dm/route_performance_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dm/route_performance_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#32-dmroute_performance"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dm.monthly_overview",
|
||
"name": "dm.monthly_overview",
|
||
"layer": "dm",
|
||
"order": 6,
|
||
"what": "Показывает помесячные показатели по моделям самолетов.",
|
||
"grain": "Одна модель самолета за месяц.",
|
||
"keys": "`year_actual` + `month_actual` + `airplane_sk` — логический ключ агрегата.",
|
||
"fields": [
|
||
{
|
||
"name": "year_actual",
|
||
"meaning": "год"
|
||
},
|
||
{
|
||
"name": "month_actual",
|
||
"meaning": "месяц"
|
||
},
|
||
{
|
||
"name": "airplane_sk",
|
||
"meaning": "модель"
|
||
},
|
||
{
|
||
"name": "total_flights",
|
||
"meaning": "число уникальных рейсов"
|
||
},
|
||
{
|
||
"name": "avg_load_factor",
|
||
"meaning": "средняя заполняемость по рейсам"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: пересчитывать затронутые месяцы; сначала считать показатели рейса, затем месяца.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dm/monthly_overview_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dm/monthly_overview_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dm/monthly_overview_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#33-dmmonthly_overview"
|
||
}
|
||
],
|
||
"parts": []
|
||
},
|
||
{
|
||
"id": "dm.passenger_loyalty",
|
||
"name": "dm.passenger_loyalty",
|
||
"layer": "dm",
|
||
"order": 8,
|
||
"what": "Собирает историю перелетов и трат пассажира.",
|
||
"grain": "Один пассажир за всю историю.",
|
||
"keys": "`passenger_sk` — логический ключ агрегата.",
|
||
"fields": [
|
||
{
|
||
"name": "passenger_sk",
|
||
"meaning": "ключ пассажира"
|
||
},
|
||
{
|
||
"name": "passenger_bk",
|
||
"meaning": "исходный passenger_id"
|
||
},
|
||
{
|
||
"name": "total_bookings",
|
||
"meaning": "уникальные бронирования"
|
||
},
|
||
{
|
||
"name": "total_flights",
|
||
"meaning": "число сегментов пассажира"
|
||
},
|
||
{
|
||
"name": "total_spent",
|
||
"meaning": "сумма цен"
|
||
},
|
||
{
|
||
"name": "favorite_fare_conditions",
|
||
"meaning": "самый частый класс обслуживания"
|
||
}
|
||
],
|
||
"fill": "По ТЗ: пересчитывать всю историю пассажиров, чьи факты изменились. Строки без `passenger_sk` исключаются.",
|
||
"status": "student",
|
||
"links": [
|
||
{
|
||
"label": "DDL",
|
||
"url": "../../sql/dm/passenger_loyalty_ddl.sql"
|
||
},
|
||
{
|
||
"label": "Загрузка · заглушка",
|
||
"url": "../../sql/dm/passenger_loyalty_load.sql"
|
||
},
|
||
{
|
||
"label": "DQ · заглушка",
|
||
"url": "../../sql/dm/passenger_loyalty_dq.sql"
|
||
},
|
||
{
|
||
"label": "Задание",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
],
|
||
"parts": []
|
||
}
|
||
],
|
||
"edges": [
|
||
{
|
||
"id": "flow-bookings.bookings-stg.bookings-0",
|
||
"from": "bookings.bookings",
|
||
"to": "stg.bookings",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.bookings_ext` читает `bookings.bookings` без локальной копии. Загрузчик переносит выбранные строки в `stg.bookings`. Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/bookings_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.bookings-ods.bookings-1",
|
||
"from": "stg.bookings",
|
||
"to": "ods.bookings",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/bookings_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.tickets-stg.tickets-2",
|
||
"from": "bookings.tickets",
|
||
"to": "stg.tickets",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.tickets_ext` читает `bookings.tickets` без локальной копии. Загрузчик переносит выбранные строки в `stg.tickets`. Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Уже сохраненный `ticket_no` повторно не вставляется.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/tickets_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.tickets-ods.tickets-3",
|
||
"from": "stg.tickets",
|
||
"to": "ods.tickets",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. `outbound` становится `is_outbound` (BOOLEAN).",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/tickets_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.segments-stg.segments-4",
|
||
"from": "bookings.segments",
|
||
"to": "stg.segments",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.segments_ext` читает `bookings.segments` без локальной копии. Загрузчик переносит выбранные строки в `stg.segments`. Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/segments_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.segments-ods.segments-5",
|
||
"from": "stg.segments",
|
||
"to": "ods.segments",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. `stg.segments.price` становится `amount` (NUMERIC).",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/segments_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.flights-stg.flights-6",
|
||
"from": "bookings.flights",
|
||
"to": "stg.flights",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.flights_ext` читает `bookings.flights` без локальной копии. Загрузчик переносит выбранные строки в `stg.flights`. Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `scheduled_departure`. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/flights_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.flights-ods.flights-7",
|
||
"from": "stg.flights",
|
||
"to": "ods.flights",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG. Дополнительно берутся рейсы из истории STG, на которые ссылаются новые `stg.segments`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/flights_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.boarding_passes-stg.boarding_passes-8",
|
||
"from": "bookings.boarding_passes",
|
||
"to": "stg.boarding_passes",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.boarding_passes_ext` читает `bookings.boarding_passes` без локальной копии. Загрузчик переносит выбранные строки в `stg.boarding_passes`. Добавление строк по `event_ts` предыдущих загрузок. Опорное время — `book_date` бронирования. Повтор защищен ключом строки внутри `_load_id`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/boarding_passes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.boarding_passes-ods.boarding_passes-9",
|
||
"from": "stg.boarding_passes",
|
||
"to": "ods.boarding_passes",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Дельта из STG по `_load_ts`, приведение типов, выбор последней строки по бизнес-ключу; UPDATE изменившихся и INSERT новых. Время и идентификатор загрузки сохраняются из STG.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/boarding_passes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.routes-stg.routes-10",
|
||
"from": "bookings.routes",
|
||
"to": "stg.routes",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.routes_ext` читает `bookings.routes` без локальной копии. Загрузчик переносит выбранные строки в `stg.routes`. Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/routes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.routes-ods.routes-11",
|
||
"from": "stg.routes",
|
||
"to": "ods.routes",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ. `scheduled_time` становится `departure_time` (TIME), `days_of_week` — INTEGER[], `duration` — INTERVAL.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/routes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.airports_data-stg.airports-12",
|
||
"from": "bookings.airports_data",
|
||
"to": "stg.airports",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.airports_ext` читает `bookings.airports_data` без локальной копии. Загрузчик переносит выбранные строки в `stg.airports`. Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airports_data",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/airports_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.airports-ods.airports-13",
|
||
"from": "stg.airports",
|
||
"to": "ods.airports",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ. Из JSON извлекаются русские названия аэропорта, города и страны.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/airports_load.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#ods-airports"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.airplanes_data-stg.airplanes-14",
|
||
"from": "bookings.airplanes_data",
|
||
"to": "stg.airplanes",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.airplanes_ext` читает `bookings.airplanes_data` без локальной копии. Загрузчик переносит выбранные строки в `stg.airplanes`. Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airplanes_data",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/airplanes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.airplanes-ods.airplanes-15",
|
||
"from": "stg.airplanes",
|
||
"to": "ods.airplanes",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Ожидается по ТЗ: Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "stg.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#11-odsairplanes"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.seats-stg.seats-16",
|
||
"from": "bookings.seats",
|
||
"to": "stg.seats",
|
||
"label": "Чтение через PXF",
|
||
"join": "`stg.seats_ext` читает `bookings.seats` без локальной копии. Загрузчик переносит выбранные строки в `stg.seats`. Полный снимок при каждом новом запуске STG; повтор в том же `_load_id` не добавляет строку повторно.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/seats_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.seats-ods.seats-17",
|
||
"from": "stg.seats",
|
||
"to": "ods.seats",
|
||
"label": "Типизация и загрузка ODS",
|
||
"join": "Ожидается по ТЗ: Полный снимок из согласованного батча STG (`stg_batch_id`): TRUNCATE + INSERT, одна строка на бизнес-ключ.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "stg.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#12-odsseats"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.bookings-stg.tickets-18",
|
||
"from": "bookings.bookings",
|
||
"to": "stg.tickets",
|
||
"label": "Дата отбора",
|
||
"join": "`book_date` из `stg.bookings_ext` определяет окно. INNER JOIN по `book_ref` с билетом. Без бронирования строка не проходит соединение.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.bookings",
|
||
"columns": [
|
||
"book_ref",
|
||
"book_date"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/tickets_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.bookings-stg.segments-19",
|
||
"from": "bookings.bookings",
|
||
"to": "stg.segments",
|
||
"label": "Дата отбора",
|
||
"join": "`book_date` из `stg.bookings_ext` определяет окно. Сегмент/талон соединяется с `stg.tickets_ext` по `ticket_no`, затем с `stg.bookings_ext` по `book_ref`. Без бронирования строка не проходит соединение.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.bookings",
|
||
"columns": [
|
||
"book_ref",
|
||
"book_date"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/segments_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.bookings-stg.boarding_passes-20",
|
||
"from": "bookings.bookings",
|
||
"to": "stg.boarding_passes",
|
||
"label": "Дата отбора",
|
||
"join": "`book_date` из `stg.bookings_ext` определяет окно. Сегмент/талон соединяется с `stg.tickets_ext` по `ticket_no`, затем с `stg.bookings_ext` по `book_ref`. Без бронирования строка не проходит соединение.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.bookings",
|
||
"columns": [
|
||
"book_ref",
|
||
"book_date"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/boarding_passes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.tickets-stg.segments-21",
|
||
"from": "bookings.tickets",
|
||
"to": "stg.segments",
|
||
"label": "Билет для отбора",
|
||
"join": "INNER JOIN по `ticket_no` дает `book_ref`, через него находится дата бронирования. Без билета строка не попадет в STG.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.segments",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/segments_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-bookings.tickets-stg.boarding_passes-22",
|
||
"from": "bookings.tickets",
|
||
"to": "stg.boarding_passes",
|
||
"label": "Билет для отбора",
|
||
"join": "INNER JOIN по `ticket_no` дает `book_ref`, через него находится дата бронирования. Без билета строка не попадет в STG.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.boarding_passes",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/stg/boarding_passes_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-stg.segments-ods.flights-23",
|
||
"from": "stg.segments",
|
||
"to": "ods.flights",
|
||
"label": "Рейсы новых сегментов",
|
||
"join": "`flight_id` новых сегментов определяет, какие рейсы дополнительно взять из истории `stg.flights`. Если рейса там нет, эта ветка его не создаст; связь проверяет DQ сегментов.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "stg.segments",
|
||
"columns": [
|
||
"flight_id",
|
||
"_load_ts"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/flights_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.airports-dds.dim_airports-24",
|
||
"from": "ods.airports",
|
||
"to": "dds.dim_airports",
|
||
"label": "Код аэропорта → SK",
|
||
"join": "`airport_code = airport_bk`: атрибуты существующего аэропорта обновляются, для нового кода создается строка с SK.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_bk",
|
||
"airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/dim_airports_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.segments-dds.dim_tariffs-25",
|
||
"from": "ods.segments",
|
||
"to": "dds.dim_tariffs",
|
||
"label": "Уникальные классы",
|
||
"join": "Из `fare_conditions` выбираются уникальные непустые значения. Новому классу назначается `tariff_sk`; отсутствие сегментов не удаляет старые классы.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"fare_conditions"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_tariffs",
|
||
"columns": [
|
||
"fare_conditions",
|
||
"tariff_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/dim_tariffs_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.airplanes-dds.dim_airplanes-26",
|
||
"from": "ods.airplanes",
|
||
"to": "dds.dim_airplanes",
|
||
"label": "Описание модели",
|
||
"join": "По ТЗ: `airplane_code` становится `airplane_bk`; характеристики модели получают свой SK.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_bk",
|
||
"airplane_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#21-ddsdim_airplanes-scd1"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.seats-dds.dim_airplanes-27",
|
||
"from": "ods.seats",
|
||
"to": "dds.dim_airplanes",
|
||
"label": "Вместимость модели",
|
||
"join": "По ТЗ: места считаются по `airplane_code` и добавляют `total_seats`. LEFT JOIN сохраняет модель без совпавших мест. Точное значение для отсутствующих мест ТЗ не оговаривает; нулевую вместимость нельзя трактовать как известную.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_bk",
|
||
"total_seats"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#21-ddsdim_airplanes-scd1"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.tickets-dds.dim_passengers-28",
|
||
"from": "ods.tickets",
|
||
"to": "dds.dim_passengers",
|
||
"label": "Пассажиры из билетов",
|
||
"join": "По ТЗ: несколько билетов одного `passenger_id` дают одного пассажира с актуальным именем. Отдельного JOIN к справочнику пассажиров источника нет.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"passenger_id",
|
||
"passenger_name"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_passengers",
|
||
"columns": [
|
||
"passenger_id",
|
||
"passenger_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#22-ddsdim_passengers-scd1"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.routes-dds.dim_routes-29",
|
||
"from": "ods.routes",
|
||
"to": "dds.dim_routes",
|
||
"label": "История маршрута",
|
||
"join": "По ТЗ: `route_no` становится `route_bk`; изменения атрибутов создают новую версию с `route_sk` и интервалом действия. Исчезновение маршрута закрывает открытую версию.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"route_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_bk",
|
||
"route_sk",
|
||
"valid_from",
|
||
"valid_to"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#23-ddsdim_routes-scd2"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_airports-dds.dim_routes-30",
|
||
"from": "dds.dim_airports",
|
||
"to": "dds.dim_routes",
|
||
"label": "Города вылета и прилета",
|
||
"join": "По ТЗ: коды `departure_airport` и `arrival_airport` сопоставляются с `airport_bk`; добавляются два города. Тип JOIN при пропуске ТЗ не задает, но города в DDL обязательны: пропуск нельзя молча записать как NULL.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_bk",
|
||
"city"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"departure_airport",
|
||
"arrival_airport",
|
||
"departure_city",
|
||
"arrival_city"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#23-ddsdim_routes-scd2"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_airplanes-dds.dim_routes-31",
|
||
"from": "dds.dim_airplanes",
|
||
"to": "dds.dim_routes",
|
||
"label": "Модель и места",
|
||
"join": "По ТЗ: `airplane_code = airplane_bk` дает `airplane_model` и `total_seats`. Тип JOIN при пропуске не определен; оба поля в DDL обязательны. Это требуемое обогащение, загрузчик пока пуст.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_bk",
|
||
"model",
|
||
"total_seats"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"airplane_code",
|
||
"airplane_model",
|
||
"total_seats"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#23-ddsdim_routes-scd2"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-0",
|
||
"from": "ods.segments",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Зерно и цена",
|
||
"join": "Основной поток: пара `(ticket_no, flight_id)` и `amount → price`. Нет сегмента — нет новой строки факта.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id",
|
||
"amount"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id",
|
||
"price"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-1",
|
||
"from": "ods.tickets",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Билет и пассажир",
|
||
"join": "INNER JOIN `tkt.ticket_no = seg.ticket_no` дает `book_ref` и `passenger_id` для дальнейших соединений. Без билета сегмент не попадет в новый факт.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"ticket_no",
|
||
"book_ref",
|
||
"passenger_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-2",
|
||
"from": "ods.bookings",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Дата бронирования",
|
||
"join": "INNER JOIN `bkg.book_ref = tkt.book_ref` дает `book_date::DATE`. Без бронирования сегмент не попадет в новый факт; `total_amount` здесь не используется.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.bookings",
|
||
"columns": [
|
||
"book_ref",
|
||
"book_date"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"book_date"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-3",
|
||
"from": "ods.flights",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Рейс, дата и маршрут",
|
||
"join": "INNER JOIN `flt.flight_id = seg.flight_id` дает `scheduled_departure` и `route_no` для lookup. Без рейса сегмент не попадет в новый факт.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"flight_id",
|
||
"scheduled_departure",
|
||
"route_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-4",
|
||
"from": "ods.routes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Коды двух аэропортов",
|
||
"join": "Из ODS выбирается одна строка на `route_no` с наибольшим `validity` (поле TEXT). LEFT JOIN по `flt.route_no` дает коды вылета и прилета. Это отдельный путь от SCD2: дата рейса здесь не участвует. Если маршрута нет, оба airport_sk останутся NULL; DQ считает это аномалией, а не учебной заглушкой.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity",
|
||
"departure_airport",
|
||
"arrival_airport"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"route_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"departure_airport_sk",
|
||
"arrival_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#fact-joins"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-5",
|
||
"from": "dds.dim_routes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Версия на дату рейса",
|
||
"join": "LEFT JOIN `rte.route_bk = flt.route_no` и `scheduled_departure::DATE >= valid_from`, а конец — `valid_to IS NULL` или дата `< valid_to`. Дает `route_sk` и `airplane_code` для поиска модели. Нет подходящей версии — route_sk и airplane_sk NULL, строка факта сохраняется. На main это ожидается до выполнения задания.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_bk",
|
||
"route_sk",
|
||
"airplane_code",
|
||
"valid_from",
|
||
"valid_to"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"route_no",
|
||
"scheduled_departure"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"route_sk",
|
||
"airplane_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#fact-joins"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-6",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Дата вылета",
|
||
"join": "LEFT JOIN `date_actual = scheduled_departure::DATE` дает `calendar_sk`. Нет даты в календаре — SK будет NULL, строка факта сохраняется. Это не дата бронирования.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"date_actual",
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"scheduled_departure"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-7",
|
||
"from": "dds.dim_airports",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Аэропорт вылета",
|
||
"join": "LEFT JOIN `dep.airport_bk = ods_rte.departure_airport` дает `departure_airport_sk`. Без аэропорта SK будет NULL, факт сохраняется. Код берется через ods.routes; пустой dim_routes этому не мешает.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_bk",
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"departure_airport"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"departure_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#fact-joins"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-8",
|
||
"from": "dds.dim_airports",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Аэропорт прилета",
|
||
"join": "LEFT JOIN `arr.airport_bk = ods_rte.arrival_airport` дает `arrival_airport_sk`. Без аэропорта SK будет NULL, факт сохраняется. Это вторая роль того же измерения.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_bk",
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"arrival_airport"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"arrival_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
},
|
||
{
|
||
"label": "Разбор SQL загрузки",
|
||
"url": "reading_the_pipeline.md#fact-joins"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-9",
|
||
"from": "dds.dim_airplanes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Модель выбранной версии",
|
||
"join": "LEFT JOIN `ap.airplane_bk = rte.airplane_code` дает `airplane_sk`. Код берется из выбранной по дате версии dim_routes. Без версии или модели SK будет NULL; факт сохраняется.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_bk",
|
||
"airplane_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"airplane_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-10",
|
||
"from": "dds.dim_tariffs",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Класс обслуживания",
|
||
"join": "LEFT JOIN `tar.fare_conditions = seg.fare_conditions` дает `tariff_sk`. Нет совпадения — факт сохраняется с NULL; последующая DQ факта считает такой NULL ошибкой.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_tariffs",
|
||
"columns": [
|
||
"fare_conditions",
|
||
"tariff_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"fare_conditions"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-11",
|
||
"from": "dds.dim_passengers",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Пассажир",
|
||
"join": "LEFT JOIN `pax.passenger_id = tkt.passenger_id` дает `passenger_sk`. Без пассажира факт сохраняется с NULL; на main это ожидается до выполнения задания.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_passengers",
|
||
"columns": [
|
||
"passenger_id",
|
||
"passenger_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"passenger_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"passenger_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "fact-12",
|
||
"from": "ods.boarding_passes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Посадка и место",
|
||
"join": "LEFT JOIN по ОБОИМ полям `ticket_no` и `flight_id` дает `seat_no`; наличие талона означает `is_boarded = true`. Без талона `seat_no = NULL`, `is_boarded = false`, сегмент остается в факте.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id",
|
||
"seat_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"seat_no",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.fact_flight_sales-dm.sales_report-45",
|
||
"from": "dds.fact_flight_sales",
|
||
"to": "dm.sales_report",
|
||
"label": "Агрегация сегментов",
|
||
"join": "Один день вылета × аэропорт вылета × аэропорт прилета × класс обслуживания. По HWM факта выбираются затронутые даты; за эти даты агрегируются все факты, выполняются UPDATE и INSERT. INNER JOIN к календарю, двум ролям аэропортов и тарифу: факты без этих совпадений не входят в агрегат. `tickets_sold = COUNT(*)`, `total_revenue = SUM(price)`.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"price",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dm/sales_report_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_calendar-dm.sales_report-46",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dm.sales_report",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает дату и календарные признаки по calendar_sk. INNER JOIN: без совпадения факт исключается из агрегата.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dm/sales_report_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_airports-dm.sales_report-47",
|
||
"from": "dds.dim_airports",
|
||
"to": "dm.sales_report",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает коды и города для departure_airport_sk и arrival_airport_sk; один справочник используется дважды. INNER JOIN: без совпадения факт исключается из агрегата.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"departure_airport_sk",
|
||
"arrival_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dm/sales_report_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_tariffs-dm.sales_report-48",
|
||
"from": "dds.dim_tariffs",
|
||
"to": "dm.sales_report",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает класс обслуживания по tariff_sk. INNER JOIN: без совпадения факт исключается из агрегата.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_tariffs",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dm/sales_report_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.fact_flight_sales-dm.airport_traffic-49",
|
||
"from": "dds.fact_flight_sales",
|
||
"to": "dm.airport_traffic",
|
||
"label": "Агрегация сегментов",
|
||
"join": "Один аэропорт за день. По ТЗ: пересчитывать затронутые дни. Каждый факт участвует в двух ролях. Выручку вылетов и прилетов нельзя складывать: получится двойной счет. Дата обеих ролей берется из календаря факта (дата вылета). Без фактов нет входных строк для агрегата.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"price",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#31-dmairport_traffic"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_calendar-dm.airport_traffic-50",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dm.airport_traffic",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает дату и календарные признаки по calendar_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#31-dmairport_traffic"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_airports-dm.airport_traffic-51",
|
||
"from": "dds.dim_airports",
|
||
"to": "dm.airport_traffic",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает коды и города для departure_airport_sk и arrival_airport_sk; один справочник используется дважды. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"departure_airport_sk",
|
||
"arrival_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#31-dmairport_traffic"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.fact_flight_sales-dm.route_performance-52",
|
||
"from": "dds.fact_flight_sales",
|
||
"to": "dm.route_performance",
|
||
"label": "Агрегация сегментов",
|
||
"join": "Один маршрут за всю историю, объединяя его версии. По ТЗ: полный пересчет; факты всех версий группируются по бизнес-ключу маршрута, описания берутся из открытой версии. Без фактов нет входных строк для агрегата.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"price",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#32-dmroute_performance"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_routes-dm.route_performance-53",
|
||
"from": "dds.dim_routes",
|
||
"to": "dm.route_performance",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает route_bk по route_sk для объединения версий маршрута. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#32-dmroute_performance"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_calendar-dm.route_performance-54",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dm.route_performance",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает дату и календарные признаки по calendar_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#32-dmroute_performance"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.fact_flight_sales-dm.monthly_overview-55",
|
||
"from": "dds.fact_flight_sales",
|
||
"to": "dm.monthly_overview",
|
||
"label": "Агрегация сегментов",
|
||
"join": "Одна модель самолета за месяц. По ТЗ: пересчитывать затронутые месяцы; сначала считать показатели рейса, затем месяца. Без фактов нет входных строк для агрегата.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"price",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#33-dmmonthly_overview"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_calendar-dm.monthly_overview-56",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dm.monthly_overview",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает дату и календарные признаки по calendar_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#33-dmmonthly_overview"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_airplanes-dm.monthly_overview-57",
|
||
"from": "dds.dim_airplanes",
|
||
"to": "dm.monthly_overview",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает название модели и вместимость по airplane_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"airplane_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#33-dmmonthly_overview"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_routes-dm.monthly_overview-58",
|
||
"from": "dds.dim_routes",
|
||
"to": "dm.monthly_overview",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает route_bk по route_sk для объединения версий маршрута. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#33-dmmonthly_overview"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.fact_flight_sales-dm.passenger_loyalty-59",
|
||
"from": "dds.fact_flight_sales",
|
||
"to": "dm.passenger_loyalty",
|
||
"label": "Агрегация сегментов",
|
||
"join": "Один пассажир за всю историю. По ТЗ: пересчитывать всю историю пассажиров, чьи факты изменились. Строки без `passenger_sk` исключаются. Без фактов нет входных строк для агрегата.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"price",
|
||
"is_boarded"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_passengers-dm.passenger_loyalty-60",
|
||
"from": "dds.dim_passengers",
|
||
"to": "dm.passenger_loyalty",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает исходный идентификатор и имя по passenger_sk. По ТЗ факты с NULL passenger_sk исключаются; загрузка пока заглушка.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_passengers",
|
||
"columns": [
|
||
"passenger_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"passenger_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_tariffs-dm.passenger_loyalty-61",
|
||
"from": "dds.dim_tariffs",
|
||
"to": "dm.passenger_loyalty",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает класс обслуживания по tariff_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_tariffs",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_routes-dm.passenger_loyalty-62",
|
||
"from": "dds.dim_routes",
|
||
"to": "dm.passenger_loyalty",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает route_bk по route_sk для объединения версий маршрута. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-dds.dim_calendar-dm.passenger_loyalty-63",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dm.passenger_loyalty",
|
||
"label": "Измерение для витрины",
|
||
"join": "Дает дату и календарные признаки по calendar_sk. Ожидаемая связь по ТЗ; загрузка пока заглушка. Тип JOIN и общее поведение при пропуске измерения ТЗ не задает.",
|
||
"mode": "flow",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../assignment/analyst_spec.md#34-dmpassenger_loyalty"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.bookings-bookings.tickets-64",
|
||
"from": "bookings.bookings",
|
||
"to": "bookings.tickets",
|
||
"label": "Бронирование → билеты",
|
||
"join": "`bookings.book_ref = tickets.book_ref`: одно бронирование объединяет несколько билетов. Само сопоставление ничего не загружает. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.tickets-bookings.segments-65",
|
||
"from": "bookings.tickets",
|
||
"to": "bookings.segments",
|
||
"label": "Билет → сегменты",
|
||
"join": "`tickets.ticket_no = segments.ticket_no`: билет с пересадкой дает несколько сегментов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.segments",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.flights-bookings.segments-66",
|
||
"from": "bookings.flights",
|
||
"to": "bookings.segments",
|
||
"label": "Рейс → сегменты",
|
||
"join": "`flights.flight_id = segments.flight_id`: один рейс связан с сегментами разных билетов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.segments",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.segments-bookings.boarding_passes-67",
|
||
"from": "bookings.segments",
|
||
"to": "bookings.boarding_passes",
|
||
"label": "Сегмент → талон",
|
||
"join": "Совпадают `ticket_no` И `flight_id`. У сегмента может не быть талона; талон другого рейса того же билета не подходит. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.routes-bookings.flights-68",
|
||
"from": "bookings.routes",
|
||
"to": "bookings.flights",
|
||
"label": "Маршрут → рейсы",
|
||
"join": "`routes.route_no = flights.route_no` связывает рейс с маршрутом, но route_no в routes не уникален: есть варианты validity. Простой JOIN только по номеру может размножить рейс. В сборке факта ODS-маршрут сначала сокращается до одной строки. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.flights",
|
||
"columns": [
|
||
"route_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.airports_data-bookings.routes-69",
|
||
"from": "bookings.airports_data",
|
||
"to": "bookings.routes",
|
||
"label": "Аэропорт вылета → маршруты",
|
||
"join": "`airports_data.airport_code = routes.departure_airport`: один аэропорт является местом вылета многих маршрутов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airports_data",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.routes",
|
||
"columns": [
|
||
"departure_airport"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.airports_data-bookings.routes-70",
|
||
"from": "bookings.airports_data",
|
||
"to": "bookings.routes",
|
||
"label": "Аэропорт прилета → маршруты",
|
||
"join": "`airports_data.airport_code = routes.arrival_airport`: тот же справочник играет роль места прилета. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airports_data",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.routes",
|
||
"columns": [
|
||
"arrival_airport"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.airplanes_data-bookings.seats-71",
|
||
"from": "bookings.airplanes_data",
|
||
"to": "bookings.seats",
|
||
"label": "Модель → места",
|
||
"join": "`airplanes_data.airplane_code = seats.airplane_code`: одной модели соответствует много мест. Номер seat_no сам по себе модель не определяет. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airplanes_data",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-bookings.airplanes_data-bookings.routes-72",
|
||
"from": "bookings.airplanes_data",
|
||
"to": "bookings.routes",
|
||
"label": "Модель → маршруты",
|
||
"join": "`airplanes_data.airplane_code = routes.airplane_code`: одна модель используется на разных маршрутах. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "bookings.airplanes_data",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "bookings.routes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../bookings/README.md"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.bookings-ods.tickets-73",
|
||
"from": "ods.bookings",
|
||
"to": "ods.tickets",
|
||
"label": "Бронирование → билеты",
|
||
"join": "`bookings.book_ref = tickets.book_ref`: одно бронирование объединяет несколько билетов. Само сопоставление ничего не загружает. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.bookings",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"book_ref"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/tickets_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.tickets-ods.segments-74",
|
||
"from": "ods.tickets",
|
||
"to": "ods.segments",
|
||
"label": "Билет → сегменты",
|
||
"join": "`tickets.ticket_no = segments.ticket_no`: билет с пересадкой дает несколько сегментов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.tickets",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/segments_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.flights-ods.segments-75",
|
||
"from": "ods.flights",
|
||
"to": "ods.segments",
|
||
"label": "Рейс → сегменты",
|
||
"join": "`flights.flight_id = segments.flight_id`: один рейс связан с сегментами разных билетов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/segments_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.segments-ods.boarding_passes-76",
|
||
"from": "ods.segments",
|
||
"to": "ods.boarding_passes",
|
||
"label": "Сегмент → талон",
|
||
"join": "Совпадают `ticket_no` И `flight_id`. У сегмента может не быть талона; талон другого рейса того же билета не подходит. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.boarding_passes",
|
||
"columns": [
|
||
"ticket_no",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/boarding_passes_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.routes-ods.flights-77",
|
||
"from": "ods.routes",
|
||
"to": "ods.flights",
|
||
"label": "Маршрут → рейсы",
|
||
"join": "`routes.route_no = flights.route_no` связывает рейс с маршрутом, но route_no в routes не уникален: есть варианты validity. Простой JOIN только по номеру может размножить рейс. В сборке факта ODS-маршрут сначала сокращается до одной строки. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"route_no",
|
||
"validity"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.flights",
|
||
"columns": [
|
||
"route_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/flights_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.airports-ods.routes-78",
|
||
"from": "ods.airports",
|
||
"to": "ods.routes",
|
||
"label": "Аэропорт вылета → маршруты",
|
||
"join": "`airports.airport_code = routes.departure_airport`: один аэропорт является местом вылета многих маршрутов. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"departure_airport"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/routes_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.airports-ods.routes-79",
|
||
"from": "ods.airports",
|
||
"to": "ods.routes",
|
||
"label": "Аэропорт прилета → маршруты",
|
||
"join": "`airports.airport_code = routes.arrival_airport`: тот же справочник играет роль места прилета. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airports",
|
||
"columns": [
|
||
"airport_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"arrival_airport"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/routes_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.airplanes-ods.seats-80",
|
||
"from": "ods.airplanes",
|
||
"to": "ods.seats",
|
||
"label": "Модель → места",
|
||
"join": "`airplanes.airplane_code = seats.airplane_code`: одной модели соответствует много мест. Номер seat_no сам по себе модель не определяет. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.seats",
|
||
"columns": [
|
||
"airplane_code",
|
||
"seat_no"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../../sql/ods/seats_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-ods.airplanes-ods.routes-81",
|
||
"from": "ods.airplanes",
|
||
"to": "ods.routes",
|
||
"label": "Модель → маршруты",
|
||
"join": "`airplanes.airplane_code = routes.airplane_code`: одна модель используется на разных маршрутах. Это логическая связь, не объявление FOREIGN KEY и не выбор типа JOIN.",
|
||
"mode": "keys",
|
||
"status": "student",
|
||
"refs": [
|
||
{
|
||
"node": "ods.airplanes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
},
|
||
{
|
||
"node": "ods.routes",
|
||
"columns": [
|
||
"airplane_code"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "ТЗ",
|
||
"url": "../../sql/ods/routes_ddl.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_calendar-dds.fact_flight_sales-82",
|
||
"from": "dds.dim_calendar",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Дата вылета",
|
||
"join": "`dim_calendar.calendar_sk = fact_flight_sales.calendar_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_calendar",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"calendar_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_airports-dds.fact_flight_sales-83",
|
||
"from": "dds.dim_airports",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Аэропорт вылета",
|
||
"join": "`dim_airports.airport_sk = fact_flight_sales.departure_airport_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"departure_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_airports-dds.fact_flight_sales-84",
|
||
"from": "dds.dim_airports",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Аэропорт прилета",
|
||
"join": "`dim_airports.airport_sk = fact_flight_sales.arrival_airport_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airports",
|
||
"columns": [
|
||
"airport_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"arrival_airport_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_airplanes-dds.fact_flight_sales-85",
|
||
"from": "dds.dim_airplanes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Модель самолета",
|
||
"join": "`dim_airplanes.airplane_sk = fact_flight_sales.airplane_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_airplanes",
|
||
"columns": [
|
||
"airplane_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"airplane_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_tariffs-dds.fact_flight_sales-86",
|
||
"from": "dds.dim_tariffs",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Класс обслуживания",
|
||
"join": "`dim_tariffs.tariff_sk = fact_flight_sales.tariff_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_tariffs",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"tariff_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_passengers-dds.fact_flight_sales-87",
|
||
"from": "dds.dim_passengers",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Пассажир",
|
||
"join": "`dim_passengers.passenger_sk = fact_flight_sales.passenger_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_passengers",
|
||
"columns": [
|
||
"passenger_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"passenger_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "keys-dds.dim_routes-dds.fact_flight_sales-88",
|
||
"from": "dds.dim_routes",
|
||
"to": "dds.fact_flight_sales",
|
||
"label": "Версия маршрута",
|
||
"join": "`dim_routes.route_sk = fact_flight_sales.route_sk`: одна строка измерения описывает много сегментов. LEFT JOIN при вставке факта допускает незаполненный ключ; подробный путь получения SK показан в виде «Поток данных».",
|
||
"mode": "keys",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "dds.dim_routes",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
},
|
||
{
|
||
"node": "dds.fact_flight_sales",
|
||
"columns": [
|
||
"route_sk"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/dds/fact_flight_sales_load.sql"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "flow-ods.segments-ods.flights-89",
|
||
"from": "ods.segments",
|
||
"to": "ods.flights",
|
||
"label": "Граница отбора сегментов",
|
||
"join": "`MAX(ods.segments._load_ts)` задает нижнюю границу новых `stg.segments`. Их `flight_id` помогают добрать рейсы из истории STG. Это чтение водяного знака, а не загрузка рейсов из сегментов и не зависимость задач.",
|
||
"mode": "flow",
|
||
"status": "ready",
|
||
"refs": [
|
||
{
|
||
"node": "ods.segments",
|
||
"columns": [
|
||
"_load_ts"
|
||
]
|
||
},
|
||
{
|
||
"node": "stg.segments",
|
||
"columns": [
|
||
"_load_ts",
|
||
"flight_id"
|
||
]
|
||
}
|
||
],
|
||
"links": [
|
||
{
|
||
"label": "SQL",
|
||
"url": "../../sql/ods/flights_load.sql"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|