fix(dds): починены мёртвые проверки *_not_found + правки урока 3 по ревью
- Зачем:
- перечитка урока 3 свежим взглядом нашла баг в его эталонном пути: проверки
device_not_found/geo_not_found/location_not_found в DDS никогда не срабатывали,
а текст урока ошибочно утверждал, что метки ставятся
- Что:
- sql/dds/30_ods_to_dds.sql: добавлен SETTINGS join_use_nulls=1 в оба
INSERT...SELECT. Без него LEFT JOIN на несовпадении клал в assumeNotNull(click_id)
нулевой UUID (не NULL), и if(...IS NULL, ['*_not_found'], []) молча давал []
(мёртвый код). Тот же класс бага про типы/NULL, что kafka_ts в уроке 1
- docs/course/lessons/03_ods_to_dds.md: убраны ложные claim'ы про geo_not_found/
location_not_found, формулировки приведены к реальному поведению (клик без гео
остаётся с пустыми полями NULL; целостность событий — через orphan_events);
поправлена опечатка «список всех клиентов» → «всех кликов»
- Проверка:
- синтетический тест join_use_nulls=1: клик в device без geo → в ods_parse_errors
появляются geo_not_found и geo_country_missing (до фикса — пусто)
- LIMIT=50 make transform после фикса: dds.click=26, dds.event=50,
orphan_events=0, ни одной строки с непустым ods_parse_errors (вывод не изменился —
на чистом срезе несовпадений нет)
This commit is contained in:
@@ -151,7 +151,7 @@ SELECT click_id FROM ods.geo_by_click ...
|
||||
|
||||
`UNION DISTINCT` — это «склей два списка в один и выкинь повторы». Получается полный набор
|
||||
уникальных `click_id` из обоих источников — будем называть его **универсумом кликов** (полный
|
||||
список всех клиентов, по которому дальше идём). Именно от него, а не от одной из таблиц, мы
|
||||
список всех кликов, по которому дальше идём). Именно от него, а не от одной из таблиц, мы
|
||||
строим карточки: так не потеряется клик, который есть, например, в `geo`, но почему-то не доехал
|
||||
в `device`.
|
||||
|
||||
@@ -206,15 +206,15 @@ LEFT JOIN ( ...снапшот geo... ) AS g ON g.click_id = c.click_id
|
||||
останутся пустыми (`NULL`).
|
||||
|
||||
Почему именно `LEFT`: левая таблица здесь — это полный список кликов, и **ни один клик терять
|
||||
нельзя**. Не доехало гео — ладно, сохраним клик с пустым гео и пометкой, что гео нет. Эта пометка
|
||||
тут же и ставится: рядом со сборкой стоит `if(g.click_id IS NULL, ['geo_not_found'], [])` — если
|
||||
гео не подтянулось, в список ошибок карточки добавится метка `geo_not_found`. Тот же принцип
|
||||
«не теряем и помечаем», что и `parse_errors` в ODS, только теперь про пропавшие связи.
|
||||
нельзя**. Не доехало гео — ладно, сохраним клик, а гео-поля (страна, координаты, IP) останутся
|
||||
пустыми (`NULL`). И эта пустота — уже видимый сигнал: по ней сразу понятно, что контекст по клику
|
||||
не подтянулся. Тот же принцип, что и в ODS: **не теряем, а оставляем видимый след**, — только
|
||||
теперь не про кривое поле, а про пропавшую связь между таблицами.
|
||||
|
||||
> Сущность `dds.event` (события) собирается так же, только проще: `browser` и `location`
|
||||
> связаны по `event_id` один-к-одному, и `LEFT JOIN` приклеивает к каждому событию его страницу
|
||||
> и UTM. Если `location` не доехал — событие остаётся, а поля страницы пустые с меткой
|
||||
> `location_not_found`. Разбирать этот блок построчно не будем — он повторяет ту же логику.
|
||||
> и UTM. Если `location` не доехал — событие остаётся, а поля страницы остаются пустыми. Разбирать
|
||||
> этот блок построчно не будем — он повторяет ту же логику.
|
||||
|
||||
### Сироты: событие без клика
|
||||
|
||||
@@ -231,10 +231,11 @@ WHERE click_id IS NOT NULL
|
||||
AND click_id NOT IN (SELECT click_id FROM dds.click);
|
||||
```
|
||||
|
||||
Заметь разницу с предыдущим пунктом. `geo_not_found` — это когда у **клика** нет гео (внутренний
|
||||
пропуск в карточке). А сирота — это когда у **события** нет вообще никакого клика (порвана связь
|
||||
между сущностями). Это разные дырки, и следят за ними по отдельности. На чистом срезе сирот ноль —
|
||||
сейчас мы это изменим.
|
||||
Заметь разницу с предыдущим пунктом. Пустое гео — это когда у **клика** не подтянулся свой
|
||||
контекст (внутренний пропуск в карточке, но сам клик есть). А сирота — это когда у **события**
|
||||
нет вообще никакого клика (порвана связь между сущностями). Это разные дырки: первую видно по
|
||||
пустым полям внутри карточки, вторую — отдельным счётчиком. На чистом срезе сирот ноль — сейчас
|
||||
мы это изменим.
|
||||
|
||||
---
|
||||
|
||||
@@ -333,8 +334,8 @@ make transform
|
||||
|
||||
- что такое сущность DDS и зачем собирать `dds.click` и `dds.event`, если данные уже есть в ODS;
|
||||
- почему соединяем через `LEFT JOIN`, а не обычный `JOIN`, — что было бы с кликами без гео;
|
||||
- что такое сирота и чем разрыв «событие без клика» отличается от пропуска `geo_not_found` внутри
|
||||
карточки клика.
|
||||
- что такое сирота и чем разрыв «событие без клика» отличается от пустого гео внутри карточки
|
||||
клика.
|
||||
|
||||
Если запнёшься на `argMax` — вернись к секции 3: он берёт самую свежую строку на каждый
|
||||
`click_id`, чтобы дубли `ReplacingMergeTree` не пролезли в сборку.
|
||||
@@ -354,10 +355,11 @@ make transform
|
||||
- **DDS** (этот урок) — склеили кусочки в цельные сущности `dds.click` и `dds.event` и впервые
|
||||
спросили про целостность связей между ними (сироты).
|
||||
|
||||
Заметь общий принцип всех трёх слоёв — **«не теряем, а помечаем»**. На STG не роняем приём из-за
|
||||
кривого сообщения. На ODS не выкидываем битую запись, а помечаем `parse_errors` и копим в
|
||||
`*_errors`. На DDS не выкидываем клик без гео и событие без клика, а помечаем (`geo_not_found`)
|
||||
и считаем (`orphan_events`). Один и тот же подход к качеству, проведённый через весь пайплайн.
|
||||
Заметь общий принцип всех трёх слоёв — **«не теряем, а оставляем след»**. На STG не роняем приём
|
||||
из-за кривого сообщения. На ODS не выкидываем битую запись, а помечаем `parse_errors` и копим в
|
||||
`*_errors`. На DDS не выкидываем клик без гео (оставляем его с пустыми полями) и событие без клика
|
||||
(считаем такие сироты через `orphan_events`). Один и тот же подход к качеству, проведённый через
|
||||
весь пайплайн.
|
||||
|
||||
> **Короткая заметка про DM.** За DDS есть ещё слой **DM** (Data Marts, витрины для BI) — те самые
|
||||
> `dm.v_events_enriched` и `dm.v_daily_traffic`, которыми ты только что пользовался. Сейчас они
|
||||
|
||||
Reference in New Issue
Block a user