Files
clickstream-data-platform/sql/dds/order_rebuild.sql
T
ddadminandClaude Opus 5 1e8b83ffbb feat(dds): добавлена модель заказа и пересборка окна заменой партиций
Зачем:
- Дать слою первую физическую сущность и показать идемпотентность, устроенную
  иначе, чем в приёме: заменой партиции, а не колонкой версии.
- Родить механику дневной пересборки на кластере — образец для сессий и витрин.

Что:
- Добавлены dds.order_rep/_dist, двойник-донор dds.order_stage_rep/_dist и
  публичный договор dds.order_v с разбором позиций в три массива.
- Добавлены запросы объёма прогона, сборки в двойник и замены партиции по дню.
- Добавлен даг dds_transform: группа задач order, замена — размноженной задачей.
- Окно изменяемости K пришло на сторону стенда якорем compose; владелец числа
  остаётся у генератора.
- Объём считается от позднейшего дня заказа и первого недостающего дня: ярлыки
  доставки ODS для этого непригодны, а максимум собранного дня прячет дыры.
- Записано, чего конвейер о себе не знает, и адресовано контуру DQ.

Проверка:
- make lint
- make smoke, make check-clickhouse, make check-services
- Прогон по стартовому миру: 1917 заказов, счёт равен ods.order_v, каждый заказ
  на шарде своего cityHash64(order_id), сумма позиций равна items_total.
- Повтор без нового слепка: содержимое то же, _load_id новый; после слепка
  пересобрано окно, день за окном не тронут.
- Снесённая руками партиция 2026-06-01 восстановлена побайтово.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 05:20:39 -04:00

90 lines
5.6 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- DDS: сборка дней заказа в таблицу-двойник перед заменой партиций.
--
-- Двойник очищается целиком в начале сборки, а не в конце: собранное остаётся
-- лежать до следующего запуска, и разбор происшествия видит ровно то, чем
-- подменили день. Очистка обязана быть первой ещё и по делу — вставка поверх
-- прежних строк удвоила бы день в доноре, а REPLACE PARTITION перенёс бы
-- удвоение в цель.
--
-- Дни собираются одной вставкой, а не по дню за раз: источник читается один
-- раз на весь отрезок. Кто раскладывает строки по шардам — вставка через
-- Distributed, ключ cityHash64(order_id) — разобрано в DDL двойника.
--
-- Чистит двойник DROP PARTITION ALL, а не TRUNCATE, и это не вкусовщина:
-- TRUNCATE — отдельное право, роли etl оно не выдано, а снятие всех партиций
-- входит в ALTER, который у неё есть (ADR 0007). Замерено: TRUNCATE отвечает
-- ролью etl кодом 497, DROP PARTITION ALL проходит. Расширять права ради
-- операции, для которой уже есть разрешённая форма, значило бы платить
-- доступом за синоним.
ALTER TABLE dds.order_stage_rep ON CLUSTER clickstream_cluster DROP PARTITION ALL;
INSERT INTO dds.order_stage_dist
(
order_id,
user_id,
status,
order_date,
created_at,
updated_at,
items_total,
discount,
delivery,
total,
item_sku,
item_quantity,
item_price,
_load_id,
_load_ts
)
-- Позиции разбираются здесь один раз, на границе ODS → DDS. Цена на проводе —
-- строка с двумя знаками, и JSONExtract читает её прямо в Decimal: замерено на
-- живом слепке, промежуточный toDecimal64 не нужен.
--
-- Разбор по типу молчалив, и знать об этом надо заранее. Строгий приём ODS
-- проверил только, что items — массив, внутрь он не смотрел; здесь тоже никто
-- не смотрит. Замерено: qty вида "oops" даёт 0, пропущенная цена даёт 0,
-- скаляр вместо объекта выпадает из массива целиком. На каноническом слепке
-- генератора такого не бывает, и сумма «цена × количество» сходится с
-- items_total у всех заказов, но сходится она по свойству источника, а не
-- потому, что её здесь кто-то проверяет. Постоянное место такой проверки —
-- контур DQ этапа 4; пока она делается руками при приёмке.
WITH JSONExtract(
items, 'Array(Tuple(sku String, qty UInt32, price Decimal(18, 2)))'
) AS lines
SELECT
order_id,
user_id,
status,
-- День заказа и оба времени переезжают в пояс счётчика: DDS обслуживает
-- человека с дашбордом, и пояс называется здесь в последний раз.
toDate(toTimeZone(created_at, 'Europe/Samara')),
toTimeZone(created_at, 'Europe/Samara'),
toTimeZone(updated_at, 'Europe/Samara'),
items_total,
discount,
delivery,
total,
arrayMap(line -> line.sku, lines),
arrayMap(line -> line.qty, lines),
arrayMap(line -> line.price, lines),
-- Координаты запуска, собравшего день: у всех строк дня они одни.
{load_id:String},
now64(3, 'UTC')
FROM ods.order_v
WHERE toDate(toTimeZone(created_at, 'Europe/Samara'))
BETWEEN {first_day:Date} AND {last_day:Date}
-- Вторая граница не меняет отбор, а открывает источнику отсечение
-- партиций: ODS нарезан по toDate(created_at) в UTC, и условие на
-- вычисленный день заказа для этого ключа непрозрачно — сервер прочёл бы
-- все партиции. Сутки пояса счётчика ложатся ровно на две даты UTC,
-- поэтому день слева берётся с запасом.
AND toDate(created_at) BETWEEN {first_day:Date} - 1 AND {last_day:Date}
-- Тот же ноль, что в приёме заказов: заводская двойка исполнила бы вставку
-- локально на каждом шарде, минуя ключ шардирования цели, и строки унаследовали
-- бы раскладку источника. Ключи ODS и DDS здесь совпадают, и раскладка вышла бы
-- верной — но по совпадению, а не по контракту (docs/architecture/storage.md,
-- «Раскладка по шардам», капкан #137).
SETTINGS distributed_foreground_insert = 1,
parallel_distributed_insert_select = 0;