# Модель заказа в DDS DDS готовит заказ для анализа: выбирает текущее состояние из ODS, разбирает позиции и переводит время в часовой пояс счетчика. Этот слой показывает, как собрать модель данных для витрин и повторить загрузку без удвоения заказов. ## Зерно Одна строка — один заказ в последнем известном состоянии, ключ — `order_id`. История изменений здесь не хранится. ODS тоже не служит ее архивом: промежуточные версии удаляются при фоновых слияниях. ## Состав строки Поля заказа приходят из `ods.order_v` с именами источника: `order_id`, `user_id`, `status`, `items_total`, `discount`, `delivery`, `total`. Денежные колонки хранят рубли с копейками в `Decimal(18, 2)`. `created_at` и `updated_at` переводятся из UTC в `Europe/Samara`, пояс счетчика сайта. Из `created_at` в этом поясе вычисляется `order_date` — день оформления заказа. Витрины группируют по готовой дате; правило времени общее для [хранилища](../storage.md#часовые-пояса). Строка `items` из ODS разбирается в три массива одной длины: | Колонка | Что хранит | |---|---| | `item_sku` | Артикулы товаров | | `item_quantity` | Количество каждого товара, `UInt32` | | `item_price` | Цены за единицу в рублях, `Decimal(18, 2)` | Элементы с одинаковым номером описывают одну позицию заказа. Витрина выручки разворачивает их через `ARRAY JOIN` и получает категорию товара из справочника через `dictGet`. Разбор JSON выполняется один раз при загрузке DDS. ODS проверяет только то, что `items` — массив. Разбор в DDS не заменяет проверку его содержимого: например, некорректное количество может стать нулем. Проверку суммы позиций участник добавляет в [первом задании ступени 5](../../labs/5-1-order-checks.md). ## Движок и наполнение `dds.order_rep` — таблица на `ReplicatedMergeTree`, без колонки версии. Партиция хранит один `order_date`, ключ сортировки — `order_id`. Запись через `dds.order_dist` распределяет заказы по `cityHash64(order_id)`, как в ODS. Для чтения служит `dds.order_v` с явным списком колонок над `dds.order_dist`; `FINAL` в этом слое не нужен. Даг `dds_transform` пересобирает выбранные дни из `ods.order_v`: 1. [`order_scope.sql`](../../../sql/dds/order_scope.sql) определяет непрерывный отрезок `first_day`…`last_day`. Он включает дни заказов из последнего слепка и начинается раньше, если в DDS отсутствует день, который есть в ODS. 2. [`order_rebuild.sql`](../../../sql/dds/order_rebuild.sql) очищает промежуточную таблицу `dds.order_stage_rep` и наполняет ее через `dds.order_stage_dist`. Это донор: в нем готовится результат, пока основная таблица остается доступной для чтения. 3. [`order_replace.sql`](../../../sql/dds/order_replace.sql) заменяет каждую дневную партицию основной таблицы готовой партицией донора. Повтор при неизменном ODS дает те же деловые данные без дублей — это идемпотентность загрузки. Пока заказы дня входят в семидневное окно источника, их состояние может меняться, и день пересобирается. За пределами окна новые состояния не приходят, но пропущенный день можно восстановить из ODS. Границы пересборки берутся из данных; число семь в SQL хранилища не повторяется. Замена атомарна внутри одного шарда: читатель видит прежнюю или новую партицию целиком. Замены на двух шардах независимы, поэтому между ними запрос через Distributed может увидеть смешанный результат. Донор остается до следующей сборки и доступен для диагностики. Порядок задач и повторы — в [описании дневного конвейера](../etl.md). ## Служебные колонки `_load_id` и `_load_ts` показывают, какой запуск DDS собрал строку и когда он выполнил вставку. Они одинаковы у строк одной сборки. При следующей пересборке эти метки обновятся, даже если заказ не изменился. `snapshot_date` и метки приема ODS в DDS не переносятся: они описывают получение сообщения источника. Их можно найти в ODS по `order_id`. В каждом слое служебные колонки относятся к его собственной загрузке. При поиске ошибки начните с `_load_id` в DDS: он указывает на запуск сборки. По `order_id` найдите версии заказа в ODS, а по их `_load_id` — соответствующие сырые сообщения в STG и ошибки того же приема. В таблице ошибок `order_id` может быть не разобран, поэтому нужное сообщение ищут по `raw` и координатам Kafka. Сырые сообщения хранятся около трех реальных суток, ошибки — месяц; уточнения к срокам — в [справочнике хранилища](../storage.md#срок-жизни-сырых-данных). ## Читатели - `dm.revenue_daily_v` считает выручку по заказам со статусом `paid`, разворачивает позиции и дополняет их категорией товара. - `dm.purchase_vs_orders_v` сопоставляет заказ с покупкой на сайте по `order_id`, используя `order_date`, `items_total` и `status`. - `dds.identity_map` связывает идентификаторы посетителей с `user_id` через покупки и заказы. ## Связанные решения - [ADR 0010](../../adr/0010-order-versions-in-ods.md) — версии заказов в ODS и чтение через `ods.order_v`. - [Прием заказов](../orders/ingestion.md) — проверки формата и выбор актуальной версии. - [Исследование соединений](../../research/2026-08-22-distributed-join.md) — почему сверка распределенных источников использует `GLOBAL JOIN`. - [Проталкивание условий сквозь представления](../../research/2026-08-22-view-predicate-pushdown.md) — как фильтр запроса влияет на объем чтения ODS.