# Когда хранилище узнало пользователя **Чему учит:** различать время события и время появления связи в хранилище, выбирать сведения для обогащения данных на дату расчета. Факультатив доступен после ступени 1. Понадобятся Airflow и DBeaver с подключением `student` по [инструкции](../../../README.md#подключиться-из-dbeaver). Наблюдения сохраняйте в `default` через одно подключение: ваши таблицы локальны узлу. Если предложенное имя занято, выберите свое. На сайте посетителя узнают по куке `client_id`, а магазин знает покупателя по `user_id`. Связь между ними можно установить, когда событие покупки сопоставлено с заказом. Она позволяет обогатить и прежние визиты этой куки. Вы проверите, как новый заказ меняет результат расчета за уже прошедший день. ## Соберите связи с подтверждением Поставьте `world_live` на паузу, дождитесь завершения начатых прогонов генерации и обработки. Запустите `etl_pipeline` и дождитесь успеха. На время работы мир будете продвигать вручную; новые дни останутся в стенде. Напишите запрос по двум источникам: | Источник | Нужные поля | |---|---| | `dds.event_v` | `event_type`, `event_time`, `client_id`, `purchase_id` | | `dds.order_v` | `order_id`, `user_id` | В результате нужна одна строка на пару `client_id`, `user_id` и самое раннее время найденной покупки, подтверждающей эту пару. Назовите его `first_purchase_at`. В событии `purchase` массив `purchase_id` содержит один номер заказа. Статус заказа не ограничивайте: отмена покупки не меняет владельца куки. Источники распределены по разным ключам. Для соединения используйте `GLOBAL INNER JOIN`, заключив выборки событий и заказов в подзапросы. Устройство источников описано в [карте идентичностей](../../architecture/dds/identity.md). Проверьте уникальность пар и совпадение их полного набора с `dds.identity_map_v`: одного равенства количества недостаточно. Для одной пары покажите номера заказов и события, по которым нашли время. ## Сохраните три наблюдения Создайте таблицу: ```sql CREATE TABLE default.lab_identity_history ( observation_day Date, client_id UInt64, user_id UInt64, first_purchase_at DateTime('Europe/Samara') ) ENGINE = MergeTree ORDER BY (observation_day, client_id, user_id); ``` Для метки наблюдения используйте последний обработанный день событий: ```sql SELECT max(event_date) AS observation_day FROM dds.event_v; ``` Обозначим начальную дату **H0**. Это модельная дата, а не календарный день запуска Airflow. После успешного `etl_pipeline` сохраните полный результат своего запроса через `INSERT INTO ... SELECT`, добавив эту дату. Проверьте число строк и уникальность пар за H0. Затем дважды выполните последовательность: 1. Запустите `world_next_day` с `days = 1` и дождитесь успеха. 2. Запустите `etl_pipeline` и дождитесь успеха. 3. Получите новую `observation_day`, сохраните полный набор связей и проверьте его. Получатся наблюдения **H0**, **H1** и **H2**. Каждую дату записывайте один раз: обычный `MergeTree` не устраняет повторы вставки. Между этими действиями не запускайте генерацию отдельно от наблюдения. ## Сопоставьте два времени Сравните наборы пар в соседних наблюдениях. Для нескольких новых связей покажите `client_id`, `user_id`, `first_purchase_at` и первую дату наблюдения, в которой пара появилась. Найдите связь, покупка которой произошла до дня ее обнаружения. Для одной такой связи вернитесь к заказу и событию. По данным и [порядку доставки заказов](../../architecture/orders/ingestion.md) объясните, почему связь отсутствовала в прежнем наблюдении. Был ли момент покупки достаточным основанием считать ее уже известной хранилищу? У пары, которая присутствовала уже в H0, тоже получится минимальная дата наблюдения. В этом случае она означает «пара была известна к началу опыта»: истории до H0 у вас нет. Для новых пар ежедневные наблюдения устанавливают интервал появления связи, а не точное время внутри него. ## Пересчитайте прежние визиты Возьмите один и тот же набор сессий из `dds.session_v` за даты до H0 включительно. Постройте два дневных расчета: - со связями, сохраненными в H0; - со связями, сохраненными в H2. Для каждого дня посчитайте посетителей по разным `client_id` и известных пользователей по разным `user_id`. Сессии без связи должны остаться в расчете, но неизвестный пользователь не должен увеличивать число известных. В соединении распределенных сессий с локальной историей используйте `GLOBAL LEFT JOIN` и заранее отберите в истории одну дату наблюдения. Найдите день с разным числом известных пользователей. Проследите изменение до конкретных связей и прежних сессий. Проверьте, что число посетителей не изменилось и известные пользователи не превышают его. Напишите, какой вариант нужен для двух требований к витрине: - «пересчитать прошлые визиты с учетом всех сведений, доступных сейчас»; - «воспроизвести расчет, сделанный при наблюдении H0». Для второго требования проверьте еще один способ: взять текущие связи, оставив только `first_purchase_at` до конца H0. Совпадет ли полный набор пар с сохраненным в H0? Найденным расхождением обоснуйте выбор истории для воспроизводимого расчета. ## Что должно получиться У вас есть запрос связей с подтверждающими покупками, три сохраненных наблюдения и сравнение двух расчетов на одном наборе сессий. Вы можете показать, когда новая связь стала видна в наблюдениях, и объяснить, какие данные понадобятся для воспроизведения прежнего расчета. Сохраните SQL и таблицу наблюдений: они нужны в продолжении [«Как обновлять витрину без полной пересборки»](incremental-daily-traffic.md). Если продолжать не планируете, удалите свою таблицу через `DROP TABLE`.