В витрине заказов измерение клиентов поступает с задержкой, поэтому обычное соединение исключает часть фактов. Какой механизм сохранит заказ и позволит обогатить его данными клиента позже?
CREATE TABLE orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(12, 2)
);
CREATE TABLE customers (
customer_id BIGINT,
segment VARCHAR(30)
);
INSERT INTO orders VALUES (101, 7, 250.00);
SELECT o.order_id, c.segment, o.amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id;
Используют обработку запаздывающего измерения: факт заказа сохраняют сразу, связывая его с заранее созданной записью-заглушкой вроде «неизвестный клиент», а после поступления измерения выполняют обогащение или переназначение ключа. Один только LEFT JOIN скрывает проблему в запросе, но не формирует устойчивую связь факта с будущей записью измерения.
В размерных хранилищах факты и измерения часто загружаются разными потоками. Если факт приходит раньше измерения, обычная загрузка с обязательным совпадением ключей либо теряет факт, либо оставляет его вне витрины.
Для этой проблемы применяют суррогатные ключи и специальную запись неизвестного значения. Такой подход возник как практический способ сохранить полноту фактов при независимой и неидеально синхронной загрузке источников.
В примере заказ 101 ссылается на клиента 7, но строки клиента ещё нет. INNER JOIN не возвращает заказ, поэтому сумма продаж, количество заказов и другие метрики становятся заниженными.
Простая замена соединения на LEFT JOIN покажет заказ с пустым сегментом, но не решит задачу загрузки измерения: при каждом чтении придётся отдельно обрабатывать отсутствие клиента. Кроме того, уже рассчитанные витрины или агрегаты могут не обновиться автоматически после появления измерения.
Сначала в измерении создают специальную строку, например с суррогатным ключом -1 и описанием «Неизвестный клиент». В факте хранят внешний идентификатор клиента, а поле связи с измерением временно указывает на эту строку.
После поступления клиента выполняется контролируемое обогащение: находится факт по исходному customer_id, ему назначается настоящий суррогатный ключ клиента, а агрегаты и зависимые витрины либо пересчитываются, либо обновляются дельтой.
Важно разделять бизнес-идентификатор клиента и суррогатный ключ строки измерения. Бизнес-идентификатор нужен для повторного поиска и исправления связи, а суррогатный ключ позволяет учитывать версии измерения, если применяется историзация типа SCD.
Заглушка должна иметь явное семантическое значение. Нельзя смешивать в ней неизвестного клиента, ошибочный идентификатор и клиента, который был удалён: это разные состояния, влияющие на отчётность и контроль качества данных.
Возможна и альтернативная стратегия — временно помещать факт в очередь необогащённых записей и не публиковать его в витрину до появления измерения. Она сохраняет чистоту связей, но увеличивает задержку, усложняет контроль очереди и может нарушить полноту оперативных отчётов. Заглушка обычно предпочтительна, когда полнота фактов важнее немедленной детализации.
Необходимо обеспечить идемпотентность повторной обработки. Повторный запуск не должен создавать несколько строк клиента или многократно менять агрегаты. Для этого используют уникальность по бизнес-идентификатору, журнал обработанных версий и детерминированный пересчёт затронутых периодов.
Платёжная система ежедневно загружала заказы, а справочник клиентов поступал из CRM раз в несколько часов. При INNER JOIN дневная выручка была ниже фактической, причём после появления клиентов старые агрегаты автоматически не исправлялись.
Рассматривались три варианта. Отложенная публикация заказов давала чистые данные, но задерживала отчётность. LEFT JOIN быстро показывал заказы, однако требовал специальной логики во всех потребителях. Заглушка с последующим переназначением ключа сохраняла полноту и централизовала обработку запаздывания.
Выбрали третий вариант: факты сразу связывались с ключом -1, а отдельная задача после загрузки CRM обновляла связи и пересчитывала затронутые агрегаты. В результате заказы учитывались в выручке сразу, а детализация по сегментам появлялась после доставки справочника без ручного исправления отчётов.
LEFT JOIN, чтобы корректно решить проблему?Нет. LEFT JOIN предотвращает исчезновение строки только в конкретном запросе и возвращает NULL вместо измерения. Он не создаёт устойчивого состояния в модели данных, не исправляет уже рассчитанные агрегаты и заставляет каждый потребитель самостоятельно решать, как трактовать отсутствие клиента.
Внешний идентификатор не обязан быть стабильным ключом аналитической строки. Измерение может историзироваться, объединяться или получать несколько версий, а факту потребуется ссылка на конкретную версию. Суррогатный ключ отделяет структуру источника от модели хранилища и позволяет переназначить связь без изменения бизнес-идентификатора факта.
Детализация заказа изменится, поэтому агрегат, распределённый по сегментам или другим атрибутам клиента, станет устаревшим. Нужно либо пересчитать затронутый период, либо выполнить корректирующую дельту: убрать сумму из группы «Неизвестен» и добавить её в правильную группу. Выбор зависит от объёма данных, требований к воспроизводимости и поддержки исправлений в системе витрин.