Зачем: - Полный разбор урока про идемпотентность стоял дважды, предупреждение про окно K — трижды; внимание менти конечно, и каждый пересказ списывается с этого счёта. Что: - Докстринг dds_transform сокращён до одной фразы с указателями: полный разбор остался в шапке DDL, механика подмены — в order_replace.sql. - Комментарий про ORDER_WINDOW_DAYS в даге сведён к указателю на compose, где число объявлено и где записан риск расхождения. - Прорежен повтор одного риторического приёма «это не X» — оставлен там, где различие и есть суть. Проверка: - make lint, make config-test - Прогон dds_transform зелёный, слой сходится с ods.order_v: 1917 заказов, 8 дней. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
5.1 KiB
SQL
66 lines
5.1 KiB
SQL
-- DDS: объём пересборки заказов — с какого дня по какой собирает этот запуск.
|
|
--
|
|
-- Параметров у дага нет: объём он выводит сам из состояния слоёв
|
|
-- (docs/architecture/etl.md, «Объём прогона»). Ответ — две даты, границы
|
|
-- непрерывного отрезка; список дней между ними разворачивает даг. Пустой
|
|
-- источник даёт пустой ответ, и даг на нём останавливается с внятной ошибкой
|
|
-- вместо зелёного прогона, собравшего ничего.
|
|
--
|
|
-- Файл кончается без точки с запятой: даг читает его целиком и шлёт одним
|
|
-- запросом, дописывая формат ответа, — точка с запятой сделала бы из этого
|
|
-- мультизапрос, который ClickHouse не принимает.
|
|
--
|
|
-- Объём складывается из двух частей. Первая — дышащее окно: K последних дней
|
|
-- источника, состояние которых слепок ещё может изменить. Вторая — хвост от
|
|
-- первого дня, которого в слое нет: первая сборка застаёт слой пустым и
|
|
-- собирает весь стартовый мир, а после разгона мира пачкой слой догоняет
|
|
-- уехавший вперёд источник.
|
|
--
|
|
-- **Хвост считается от первого недостающего дня, а не от последнего
|
|
-- собранного.** Разница не косметическая: замены партиций идут отдельными
|
|
-- задачами и падают порознь, поэтому упавший день оставляет дыру, пока
|
|
-- соседние дни собираются дальше. Считай мы прогресс по максимуму собранного
|
|
-- дня, дыра позади окна не заросла бы никогда, а прогоны продолжали бы
|
|
-- зеленеть над щербатым слоем. Цена честного ответа — проход по дню заказа
|
|
-- в ODS.
|
|
--
|
|
-- Край окна берётся из делового поля, а не из ярлыков доставки: время
|
|
-- создания заказа одинаково у всех его версий, а `snapshot_date` и `_load_id`
|
|
-- выжившей строки достаются жребием (docs/architecture/etl.md, «Объём
|
|
-- прогона», и docs/architecture/orders/ingestion.md, «Роль ODS»).
|
|
--
|
|
-- Известная неточность края: если в самый свежий день источника не родилось
|
|
-- ни одного заказа, окно съезжает на день назад — лишний замёрзший день
|
|
-- пересоберётся впустую, а пустой день не тронется. Данные от этого не
|
|
-- страдают, меняется только ярлык загрузки лишнего дня. Точный ответ здесь
|
|
-- дала бы дата слепка, но она и есть тот самый жребий.
|
|
WITH
|
|
-- FINAL не нужен: время рождения заказа версии не различают.
|
|
(SELECT max(toDate(toTimeZone(created_at, 'Europe/Samara')))
|
|
FROM ods.order_dist) AS last_source_day,
|
|
(SELECT min(toDate(toTimeZone(created_at, 'Europe/Samara')))
|
|
FROM ods.order_dist) AS first_source_day,
|
|
-- GLOBAL обязателен: обе таблицы распределённые, и обычный NOT IN сверял
|
|
-- бы дни источника с локальной частью слоя на каждом шарде — день, чьи
|
|
-- заказы легли на соседний шард, показался бы недостающим.
|
|
-- minOrNull отличает «дыр нет» от даты по умолчанию.
|
|
(
|
|
SELECT minOrNull(day)
|
|
FROM (
|
|
SELECT DISTINCT toDate(toTimeZone(created_at, 'Europe/Samara')) AS day
|
|
FROM ods.order_dist
|
|
)
|
|
WHERE day GLOBAL NOT IN (SELECT order_date FROM dds.order_dist)
|
|
) AS first_missing_day,
|
|
last_source_day - ({window_days:UInt16} - 1) AS window_start
|
|
SELECT
|
|
-- Дыр нет — начинаем с окна; есть — с первой из них, если она старше окна.
|
|
-- greatest держит левый край не раньше первого дня, известного источнику:
|
|
-- у молодого мира окно шире самого мира.
|
|
greatest(
|
|
least(window_start, coalesce(first_missing_day, window_start)),
|
|
first_source_day
|
|
) AS first_day,
|
|
last_source_day AS last_day
|
|
WHERE (SELECT count() FROM ods.order_dist) > 0
|