# Модель заказа в DDS Учебный результат: менти проектирует бизнес-сущность поверх готового ODS — выбирает зерно, состав строки и способ наполнения — и видит идемпотентность трансформации, устроенную иначе, чем в приёме: заменой партиции, а не версиями. ## Зерно Одна строка — заказ в текущем состоянии, ключ — `order_id`. Модель отвечает на вопрос «что сейчас известно о заказе»; историю версий не хранит: читателя у истории нет ни в одной витрине, а ODS её и не обещает — промежуточные версии съедают фоновые слияния. «Дыхание» выручки внутри окна K живёт на текущем состоянии само: пересборка дня меняет статусы заказов, и число дня меняется вместе с ними. ## Состав строки Деловые поля источника переезжают из `ods.order_v` без переименований — бэкенд уже говорит в snake_case: `order_id`, `user_id`, `status`, деньги в `Decimal` (`items_total`, `discount`, `delivery`, `total`). Времена `created_at` и `updated_at` пересчитываются при загрузке в пояс счётчика: DDS обслуживает человека с дашбордом ([дока хранилища](../storage.md), «Часовые пояса»). Там же один раз считается **день заказа** — колонка `order_date`, по образцу `EventDate` у события: суточные срезы группируются по готовой дате и пояса не упоминают. Позиции разобраны из сырой строки `items` в три типизированных массива одной длины — артикул, количество, цена (`Decimal`). Это родная для ClickHouse денормализация: позиция не живёт без заказа и собственного ключа не имеет, а разворачивает массивы читатель — `ARRAY JOIN` в витрине выручки, с обогащением категорией через `dictGet` ([мастер-спека](../../specs/2026-07-30-stand-v2-realism.md), раздел 7). Разбор JSON происходит один раз, на границе ODS → DDS, куда его и положила спека (раздел 2); записывать его параллельными колонками-массивами или `Nested` — выбор DDL, не модели. ## Движок и наполнение `MergeTree` без колонки версии: партиция — `order_date` (день заказа неизменен), ключ сортировки — `order_id`, шардирование распределённой таблицы — `cityHash64(order_id)`, тем же выражением, что у ODS: это единственный устойчивый ключ сущности. Идемпотентность держит замена партиции, а не версии. Сбор дня — батч после прихода слепка: трансформация собирает партицию заново из `ods.order_v` и атомарно подменяет. Поэтому каждая строка таблицы — ровно один заказ, и таблица сама служит поверхностью чтения слоя: голый `SELECT`, без `FINAL` и без обёртки-представления. Пока окно K живо, день «дышит» — статусы заказов меняются, и партиция дня пересобирается очередным запуском; на выходе из окна день замерзает. Механика запуска — где собирается партиция и как подменяется на кластере — сюда не входит: это устройство трансформаций (#134, этап 5). ## Служебные колонки Строка получает `_load_id` и `_load_ts` запуска трансформации, собравшего её партицию, — по общей конвенции хранилища. При замене партиции они одинаковы у всех строк дня и отвечают на вопрос «каким запуском и когда собран этот день». Ярлыки ODS — `snapshot_date`, `_load_id` приёма, `_load_ts` прибытия — в модель не переезжают. Версию заказа они не задают, а физически выжившие их значения — жребий: у строк с равным `updated_at` деловые поля совпадают побайтово, и какая переживёт схлопывание — произвол (замер — [приём заказов](../orders/ingestion.md), «Что проверено»). Перенесённый ярлык выглядел бы фактом, оставаясь жребием; лживая координата хуже отсутствующей. Отсюда правило слоя: **служебные колонки — координаты собственной загрузки слоя; чужие координаты спрашиваются у их слоя по ключу сущности**. Этим закрыто наследство #85: читатель у чужих ярлыков выше ODS не нашёлся и при названных читателях модели. Происшествие качества данных расследуется цепочкой, где каждый шаг спрашивает свой слой на его языке: по `_load_id` строки DDS видно, какой запуск собрал день; по `order_id` ODS показывает физические версии и брак — там свой `_load_id` приёма связывает годные строки, ошибки и сырьё; дальше сырьё STG, пока живо. Окно расследования задано сроками хранения: сырьё — трое суток, брак — месяц ([дока хранилища](../storage.md)). ## Читатели - Витрина выручки `dm.revenue_daily_v`: заказы в `paid`, `ARRAY JOIN` позиций, категория через `dictGet` (спека, раздел 7). - Сверка `dm.purchase_vs_orders_v`: `order_id`, `order_date`, `items_total`, `status`; соединение — GLOBAL JOIN с обеими сторонами-подзапросами ([исследование соединений](../../research/2026-08-22-distributed-join.md)). - Сборка `dds.identity_map`: пара `order_id` ↔ `user_id`. ## Не входит - Механика запуска трансформации и замены партиций на кластере — #134 и этап 5. - Контур проверок качества (даг DQ, `dm.dq_summary`) — отдельный разговор этапа 4. - Инкрементальное наполнение вместо пересборки — техдолг #2. ## Отклонённые варианты - **Представление-контракт вместо физической таблицы.** Рамка стенда — показать модель данных, которую хранилище готовит потребителям; урок «слой — контракт, а не копия» уже закреплён за `dds.event_v`, а разбор позиций и пересчёт времён при каждом чтении собирали бы модель заново на лету. - **`ReplacingMergeTree(updated_at)` ещё раз.** Повторил бы механику ODS вторым разом: физически хранил бы устаревшие состояния, вернул бы `FINAL` и обёртку-представление — а урок версий менти уже отыграл. - **Отдельная сущность `dds.order_item`.** Вторая таблица, второй вопрос шардирования и `JOIN` там, где хватает `ARRAY JOIN`; нормализация из реляционного мира, а не модель под колоночное хранилище. `JOIN` менти увидит в сверке, работу с массивами — только здесь. - **Зерно «версия заказа» (история).** Читателя нет; ODS историю не обещает, а мир при нужде переигрывается генератором. - **Перенос ярлыков ODS** — см. «Служебные колонки». - **Без модели вовсе: витрины читают `ods.order_v` напрямую.** Разбор JSON и пересчёт времён расползлись бы по витринам, а модель данных — то, ради чего сущность и строится, — не была бы показана. ## Связанные решения - [ADR 0010](../../adr/0010-order-versions-in-ods.md) — версии заказов в ODS, устойчивая поверхность `ods.order_v`. - [Приём заказов](../orders/ingestion.md) — граница строгого приёма и четыре координаты строки ODS. - [Проталкивание условий сквозь представления](../../research/2026-08-22-view-predicate-pushdown.md) — резолюция #128 той же карты: цена `FINAL` и цепочек представлений, фон, на котором здесь выбрана физическая таблица.