-- 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