diff --git a/docs/labs/README.md b/docs/labs/README.md index c607f8b..0618e4a 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -1,21 +1,29 @@ -# Учебные задания: витрины слоя DM и дашборд в Superset +# Учебные задания -Пять домашних заданий — про витрины слоя DM. Стенд привозит слой -наполовину: витрины трех сюжетов дашборда и контроль качества построены, -остальные витрины строит менти. Каждое из них — самостоятельное ТЗ: что за -витрина, какое у нее зерно, из чего она собирается и как проверить, что -собралась верно. +Программа состоит из двух частей: основная включает семь ступеней, которые вы +проходите по порядку, а факультативы выбираете по желанию. Причины такого +порядка и формы заданий описаны в [программе учебных лаб](../specs/2026-09-01-labs-program.md#2-устройство-программы-основная-программа-и-факультативы). -Шестое задание — необязательная лаба в Superset. В ней менти собирает свой -дашборд поверх пяти витрин. +## Основная программа -Общее устройство слоя — [дока DM](../architecture/dm.md). Она отвечает на -вопросы «почему так», задание отвечает на вопрос «что сделать». +| Ступень | Задание | Чему учит | +|---|---|---| +| 1 | Пять витрин слоя DM | построить результат для потребителя хранилища; уроки каждой витрины — в таблице ниже | +| 2 | [Свой график в Superset](superset-chart.md) | превратить свой результат из хранилища в отчет в BI | +| 3 | Нарежь сессии сам | счет показывает проблему, но причину приходится искать в отдельных строках | +| 4 | Перепиши нарезку стенда | изменить расчетную сущность внутри готового конвейера и проверить ее внешним эталоном | +| 5 | Сначала сторож, потом ломай: заказы | построить сторожей до рискованного изменения, а не после первой поломки | +| 6 | Разбери сырые события и сверь результат со стендом | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом | +| 7 | Мониторинг: куда смотреть | выбрать, куда и на что смотреть в живой системе | -## Порядок +ТЗ для ступеней 3–7 появятся по мере продвижения программы. -Задания идут по нарастанию: каждое следующее опирается на то, что уже -усвоено. +## Ступень 1: пять витрин слоя DM + +Слой DM в стенде построен наполовину: витрины трех сюжетов дашборда и контроль +качества уже построены, остальные пять витрин строите вы. Каждая из них — +самостоятельное ТЗ: в нем есть состав витрины, зерно, источники и способ +проверки. | № | Результат | Чему учит | |---|---|---| @@ -24,11 +32,13 @@ | 3 | [`session_overview`](session-overview.md) | зерно решает, на какие вопросы витрина уже не ответит | | 4 | [`dq_errors_daily`](dq-errors-daily.md) | у брака свой день и свой срок жизни | | 5 | [`utm_effectiveness`](utm-effectiveness.md) | `declared_` в имени — предупреждение, а не украшение | -| 6 | [Свой дашборд в Superset](superset-dashboard.md) — необязательная лаба | свои объекты BI живут отдельно от стендовых | -## Общие правила +Общее устройство слоя — [дока DM](../architecture/dm.md). Она отвечает на +вопросы «почему так», задание отвечает на вопрос «что сделать». -Они одни для пяти заданий по витринам, поэтому записаны здесь, а не +## Общие правила ступени 1 + +Они одни для пяти заданий по витринам ступени 1, поэтому записаны здесь, а не повторяются в каждом ТЗ. **Читать — через `_v`.** Публичный договор сущности DDS — представление с @@ -49,7 +59,7 @@ DDS, и задания остаются независимыми. Исключе остается обычным представлением. Довод — [дока DM](../architecture/dm.md), «Форма: по зерну, не по цене запроса». -**Ключ шардирования — твое решение.** Ни одно ТЗ ключа не называет. +**Ключ шардирования — ваше решение.** Ни одно ТЗ ключа не называет. Правило выбора и требование к донору — [дока хранилища](../architecture/storage.md), «Раскладка по шардам»: донор объявляется через `AS <цель>_rep` и шардируется тем же ключом, что цель, @@ -59,7 +69,7 @@ DDS, и задания остаются независимыми. Исключе `dm.revenue_daily_dist` в [`sql/ddl/55-dm-tables.sql`](../../sql/ddl/55-dm-tables.sql). **Где лежит код.** DDL домашек — свой файл `sql/ddl/70-labs.sql`: он -применяется после стендовых по порядку имени, и твои объекты не мешаются +применяется после стендовых по порядку имени, и ваши объекты не мешаются со стендовыми. У агрегатов рядом со стендовыми ложатся еще три запроса — `sql/dm/<витрина>_scope.sql` спрашивает у хранилища, какие дни пересобирает запуск, `_rebuild.sql` собирает их в донор, `_replace.sql` заменяет @@ -72,7 +82,7 @@ DDS, и задания остаются независимыми. Исключе Списывать с нее устройство можно и нужно; списывать состав колонок — нет, он у каждой витрины свой. -## Самопроверка +## Самопроверка ступени 1 Даг `homework_check` запускается рукой, расписания у него нет. Задача витрины красная, пока витрины нет или счет не сходится, и зеленая, когда @@ -96,11 +106,42 @@ DDS, и задания остаются независимыми. Исключе **Предупреждение.** Запросы самой проверки лежат в `sql/dq/` открыто, и имена называют витрину и роль сверки. Проверка на инвариантах неизбежно близка к ответу: чтобы сверить витрину, надо ее пересчитать. Прятать эти -файлы никто не станет — но открыв их до того, как задание сделано, ты -обменяешь урок на готовый SQL. +файлы никто не станет — но открыв их до того, как задание сделано, вы +обменяете урок на готовый SQL. Смотреть туда стоит после своей сборки: сравнить два решения полезно, списать одно — нет. +## Факультативы + +Эти задания вы берете по желанию. Полный список сюжетов — в разделе +[«Факультативы — пять разделов»](../specs/2026-09-01-labs-program.md#4-факультативы--пять-разделов) +программы. + +### Почему выручка не сходится + +Открывается после ступени 1. Расхождения между источниками, повторы +покупок и день, после которого отчет можно считать окончательным. + +### Тонкий анализ данных + +Открывается после ступени 1. Знание о пользователе на дату и обогащение +отчета своим словарем. + +### Superset и BI + +Открывается после ступени 2. [Свой дашборд в Superset](superset-dashboard.md): +свои объекты BI живут отдельно от стендовых. + +### Инженерия стенда + +Открывается после ступени 1. Прием и хранение данных, раскладка по шардам, +повторная обработка дня и обновление витрин. + +### Что делать, когда стенд сломался + +Открывается после ступени 7. Поломки стенда и runbook по итогам +расследований; весь раздел — повышенная сложность. + ## Уборка Черновики и опыты за собой сносить: правило проверок стенда — diff --git a/docs/labs/dq-errors-daily.md b/docs/labs/dq-errors-daily.md index 48bb5e0..878b017 100644 --- a/docs/labs/dq-errors-daily.md +++ b/docs/labs/dq-errors-daily.md @@ -67,7 +67,7 @@ марта удалены. Отсюда правило прогона: пересобираются **только те дни загрузки, которые -еще есть в источнике**. Спроси у источника список живых дней и замени +еще есть в источнике**. Спросите у источника список живых дней и замените партиции ровно этих дней. Соблазн «собрать все заново в донор и подменить таблицу целиком» здесь стоит дороже обычного: истекшие по `TTL` дни исчезли бы из витрины навсегда, а вместе с ними — единственная оставшаяся память о @@ -79,9 +79,9 @@ ## Ключ шардирования -Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по +Ваше решение по [правилу донора](../architecture/storage.md), «Раскладка по шардам». Витрина крошечная: классов брака у событий и заказов вместе меньше -десятка. Учти это в защите ключа — иногда правильный ответ звучит как +десятка. Учтите это в защите ключа — иногда правильный ответ звучит как «перекос здесь ничего не стоит, и вот почему». ## Как проверить себя @@ -91,8 +91,8 @@ день за границей `TTL` сверять не с чем, и его отсутствие в источнике не ошибка. -Собери прямой счет по обеим таблицам ошибок, соедини с -`dm.dq_errors_daily_v` полным внешним соединением по ключу и посчитай +Соберите прямой счет по обеим таблицам ошибок, соедините с +`dm.dq_errors_daily_v` полным внешним соединением по ключу и посчитайте строки, где ключа нет с одной из сторон или где расходится `error_rows`. Ноль — зеленый. diff --git a/docs/labs/events-enriched.md b/docs/labs/events-enriched.md index 5081b44..2c20ca7 100644 --- a/docs/labs/events-enriched.md +++ b/docs/labs/events-enriched.md @@ -91,10 +91,10 @@ ## Как проверить себя -Деловой ключ — `event_id` внутри дня. Собери прямой ответ из `dds.event_v` -за тот же день, соедини с витриной полным внешним соединением и посчитай -строки, где ключа нет с одной из сторон или где различаются значения -деловых колонок. Ноль — зеленый. +Деловой ключ — `event_id` внутри дня. Соберите прямой ответ из +`dds.event_v` за тот же день, соедините с витриной полным внешним +соединением и посчитайте строки, где ключа нет с одной из сторон или где +различаются значения деловых колонок. Ноль — зеленый. Отдельно проверяются три производные колонки: `event_hour`, `page_path` и `referer_host` должны совпасть с тем же выражением, посчитанным по diff --git a/docs/labs/session-overview.md b/docs/labs/session-overview.md index c0d9b0f..9cc5c0b 100644 --- a/docs/labs/session-overview.md +++ b/docs/labs/session-overview.md @@ -64,7 +64,7 @@ DDS уже перевел код источника в слово. Словар С `visitors` история тоньше, и в ней главная ловушка задания. Складывать уникальных по строкам нельзя: один и тот же посетитель, попавший в две -строки, будет посчитан дважды. Проверь на стенде — и увидишь, что сумма +строки, будет посчитан дважды. Проверьте на стенде — и увидите, что сумма `visitors` по устройствам за день **сходится** с `dm.daily_traffic_v`. Это не отменяет правила, а показывает, откуда берется совпадение: в мире @@ -87,17 +87,17 @@ DDS уже перевел код источника в слово. Словар ## Ключ шардирования -Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по +Ваше решение по [правилу донора](../architecture/storage.md), «Раскладка по шардам». Здесь у измерения всего три встречающихся значения, и распределены они неровно: планшеты — тонкая полоса, а телефоны и настольные машины держат -основную массу. Прежде чем брать хеш от одного `device_category`, посчитай, -сколько строк ляжет на каждый шард, и назови, чем тебя устраивает результат. +основную массу. Прежде чем брать хеш от одного `device_category`, посчитайте, +сколько строк ляжет на каждый шард, и назовите, чем вас устраивает результат. ## Как проверить себя -Деловой ключ — `device_category` внутри дня. Собери прямой агрегат из -`dds.session_v` за день, соедини с `dm.session_overview_v` полным внешним -соединением и посчитай строки, где ключа нет с одной из сторон или где +Деловой ключ — `device_category` внутри дня. Соберите прямой агрегат из +`dds.session_v` за день, соедините с `dm.session_overview_v` полным внешним +соединением и посчитайте строки, где ключа нет с одной из сторон или где расходится любой счетчик. Ноль — зеленый. Две проверки покрупнее ловят грубую ошибку сразу: diff --git a/docs/labs/superset-chart.md b/docs/labs/superset-chart.md new file mode 100644 index 0000000..e639b21 --- /dev/null +++ b/docs/labs/superset-chart.md @@ -0,0 +1,90 @@ +# Свой график в Superset + +**Чему учит:** увидеть, как собственный результат из хранилища становится +отчетом в BI. + +## Сначала прочитать + +- [«Немного Superset»](../handbook/superset.md); +- [задание 3: `dm.session_overview`](session-overview.md). + +## Роль графика + +В задании 3 вы собрали витрину `dm.session_overview_v`. Теперь покажите, с +каких устройств чаще уходят после первой страницы. Постройте столбиковый +график доли отказов по `device_category` за весь период витрины. Измерение +одно — `device_category`, метрика тоже одна. + +## Что сделать + +Заведите физический датасет в подключении к `ClickHouse`: схема `dm`, объект +`session_overview_v`. Назовите по-русски хотя бы две колонки: + +- `device_category` — «Категория устройства»; +- `report_date` — «Дата отчета». + +В редакторе датасета, на вкладке «Metrics», создайте метрику: + +| Поле | Значение | +|---|---| +| `Metric Key` | `bounce_share` — ключ метрики и ее SQL-псевдоним | +| `Label` | «Доля отказов» | +| `SQL expression` | `SUM(bounce_sessions) / SUM(sessions)` | +| `D3 format` | `.1%` | + +Доля считается из сумм слагаемых: среднее готовых долей соврет. Поэтому +метрике нужно это выражение. Сохраните ее в датасете, а не в графике: +подписи и формат задаются в датасете, а график их использует. Так во всех +будущих графиках у метрики будут одинаковые подписи. Это правило описано в +[справочнике Superset](../architecture/superset.md#дерево-импорта). + +Не используйте вкладку «Custom SQL» конструктора графика. Созданная там +метрика доступна только этому графику, а нужна метрика датасета. + +Создайте столбиковый график с метрикой «Доля отказов». В поле «X-axis» +укажите «Категория устройства», а «Dimensions» не заполняйте. Дашборд +создавать не нужно: сохраненного графика достаточно. + +По умолчанию в графике выбран период «No filter». Оставьте его таким или +задайте явные даты. Относительный период, например «Last week», даст пустой +график: модельный календарь стенда отстает от настоящего. Последнюю дату +витрины посмотрите в SQL Lab: + +```sql +SELECT max(report_date) +FROM dm.session_overview_v; +``` + +Стендовые объекты не меняйте. Что переживает `make up`, а что стирает +`make clean`, описано в главе [«Немного Superset»](../handbook/superset.md#стендовые-объекты-и-ваши). + +## Как проверить себя + +График проверяется глазами, поэтому задачи для него в `homework_check` нет. +В SQL Lab выполните запрос: + +```sql +SELECT device_category, + round(100 * sum(bounce_sessions) / sum(sessions), 1) AS bounce_pct +FROM dm.session_overview_v +GROUP BY device_category +ORDER BY device_category; +``` + +Сверьте с графиком значение для каждой категории. В разделе «Customize» +включите «Show Value» или наведите указатель на столбик. Запрос возвращает +проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть +один в один. + +## Приемка + +- [ ] создан физический датасет `dm.session_overview_v` в подключении + `ClickHouse`; +- [ ] в датасете есть метрика «Доля отказов» с выражением + `SUM(bounce_sessions) / SUM(sessions)` и форматом `.1%`; +- [ ] для `device_category` и `report_date` заданы русские имена «Категория + устройства» и «Дата отчета»; +- [ ] сохранен столбиковый график доли отказов по категориям устройств без + относительного фильтра по времени; +- [ ] график сверен с запросом из SQL Lab; +- [ ] стендовые объекты не изменены, дашборд не создан. diff --git a/docs/labs/superset-dashboard.md b/docs/labs/superset-dashboard.md index 47c36d0..d3081f4 100644 --- a/docs/labs/superset-dashboard.md +++ b/docs/labs/superset-dashboard.md @@ -1,39 +1,39 @@ -# Задание 6 (необязательное): свой дашборд в Superset +# Свой дашборд в Superset **Чему учит:** довести свои витрины до потребителя — завести в Superset датасеты поверх публичных представлений, подобрать графики под вопросы и собрать из них один связный экран. -Эта лаба идет после заданий 1–5 и опирается на их результат. Ее можно -пропустить: основной предмет стенда — ClickHouse, а здесь вся работа — в -интерфейсе BI. +Это факультатив раздела «Superset и BI». Его можно пройти после ступени 2, +когда готовы витрины ступени 1. ## Роль дашборда -Собери обзор трафика и поведения посетителей. Он должен отвечать на три +Соберите обзор трафика и поведения посетителей. Он должен отвечать на три вопроса: что происходило в магазине, какие страницы, устройства и рекламные каналы дали результат, были ли ошибки приема данных. Стендовый дашборд «Бизнес-сюжеты» собран из готовых витрин. Свой дашборд -собери из пяти витрин, которые ты построил в заданиях. Копировать раскладку -стендового дашборда не нужно: выбери форму, в которой ответы читаются без +соберите из пяти витрин, которые вы построили в заданиях. Копировать раскладку +стендового дашборда не нужно: выберите форму, в которой ответы читаются без пояснений рядом с экраном. ## Свои объекты — отдельно -Не добавляй графики в стендовый дашборд и не правь его датасеты. При +Не добавляйте графики в стендовый дашборд и не правьте его датасеты. При инициализации Superset импорт с `--overwrite` заменит ручные правки стендовых объектов файлами из репозитория. `make up` свой дашборд не стирает; срок жизни таких объектов описан в [доке Superset](../architecture/superset.md), «Импорт при каждой инициализации». -Заведи отдельные датасеты и графики, собери из них свой дашборд. Назови его, -например, «Мои витрины DM». Это то же правило, по которому DDL домашек лежит -в `sql/ddl/70-labs.sql`: стенд привозит свои объекты, менти создает свои. +Заведите отдельные датасеты и графики, соберите из них свой дашборд. +Назовите его, например, «Мои витрины DM». Это то же правило, по которому DDL +домашек лежит в `sql/ddl/70-labs.sql`: стендовые объекты уже есть, свои +создаете вы. ## Датасеты -Заведи в подключении `ClickHouse` пять физических датасетов схемы `dm`: +Заведите в подключении `ClickHouse` пять физических датасетов схемы `dm`: - `events_enriched_v`; - `top_pages_daily_v`; @@ -44,7 +44,7 @@ ## Состав дашборда Каждая из пяти витрин должна появиться хотя бы в одном графике. Точный тип -графика выбери сам. На дашборде покажи следующее: +графика выберите сами. На дашборде покажите следующее: | Витрина | Что показать | |---|---| @@ -54,7 +54,7 @@ | `dq_errors_daily_v` | число ошибок по дню загрузки, источнику и классу | | `utm_effectiveness_v` | трафик, покупки и заявленную выручку по рекламным каналам | -Для долей используй слагаемые из витрин. Например, доля отказов считается +Для долей используйте слагаемые из витрин. Например, доля отказов считается как сумма `bounce_sessions`, деленная на сумму `sessions`. Среднее по готовым долям для такого графика не подходит — урок задания 3 остается в силе. @@ -65,19 +65,20 @@ Эта лаба проверяется глазами. У расположения панелей и понятных подписей нет счетного инварианта, поэтому задачи для нее в `homework_check` нет. -Открой список дашбордов и проверь: +Откройте список дашбордов и проверьте: - «Бизнес-сюжеты» остался отдельным стендовым дашбордом; -- твой дашборд открывается без ошибок и содержит график от каждого из пяти +- ваш дашборд открывается без ошибок и содержит график от каждого из пяти датасетов; - подписи, единицы измерения и легенды читаются без обращения к SQL; - прямой трафик не исчез из графика UTM, а заявленная выручка не выдана за серверную; - пустой график ошибок нормален, если в таблицах брака сейчас нет строк. -Последний проход сделай как читатель: найди на экране самый заметный канал, -страницу и устройство, затем проверь, понятно ли из подписей, что именно -сравнивается. Если ответ приходится угадывать, поправь график или его название. +Последний проход сделайте как читатель: найдите на экране самый заметный +канал, страницу и устройство, затем проверьте, понятно ли из подписей, что +именно сравнивается. Если ответ приходится угадывать, поправьте график или +его название. ## Приемка diff --git a/docs/labs/top-pages-daily.md b/docs/labs/top-pages-daily.md index 350253b..254a3f0 100644 --- a/docs/labs/top-pages-daily.md +++ b/docs/labs/top-pages-daily.md @@ -64,7 +64,7 @@ пересобирать нужно только то, что еще не собрано: хвост дней, которых в витрине нет, — от первого несобранного прожитого дня до последнего прожитого. Готовые дни трогать незачем, а вот дыру, оставленную частичным -отказом, объем прогона обязан подобрать: ищи не последний собранный день, а +отказом, объем прогона обязан подобрать: ищите не последний собранный день, а первый несобранный. Механика — сборка отрезка в донор одной вставкой и замена дневных партиций @@ -72,16 +72,16 @@ ## Ключ шардирования -Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по -шардам». Подумай, чем эта витрина отличается от `revenue_daily`: там кроме +Ваше решение по [правилу донора](../architecture/storage.md), «Раскладка по +шардам». Подумайте, чем эта витрина отличается от `revenue_daily`: там кроме дня было одно измерение из шести близких по объему значений, здесь — страницы, которых много и которые по популярности очень неровные. ## Как проверить себя -Деловой ключ — `page_path` внутри дня. Собери прямой агрегат из -`dds.event_v` за день, соедини с `dm.top_pages_daily_v` полным внешним -соединением по ключу и посчитай строки, где ключа нет с одной из сторон или +Деловой ключ — `page_path` внутри дня. Соберите прямой агрегат из +`dds.event_v` за день, соедините с `dm.top_pages_daily_v` полным внешним +соединением по ключу и посчитайте строки, где ключа нет с одной из сторон или где расходится любой из четырех счетчиков. Ноль — зеленый. Две проверки попроще, ими удобно ловить грубую ошибку до полного @@ -94,7 +94,7 @@ Проверки ловят разное. Фильтр `WHERE event_type = 'pageview'` первая из них пропускает: отбор строк по типу не меняет счет событий того же типа. Расхождение покажет только вторая — вместе с `cart_adds` обнулятся и -`purchases`. Проверку выбирают по тому, умеет ли она покраснеть на твоей +`purchases`. Проверку выбирают по тому, умеет ли она покраснеть на вашей ошибке, а не по тому, как быстро она пишется: [проверка, которая не умеет краснеть, бесполезна](../architecture/testing.md). diff --git a/docs/labs/utm-effectiveness.md b/docs/labs/utm-effectiveness.md index 3ad86b1..3b5d467 100644 --- a/docs/labs/utm-effectiveness.md +++ b/docs/labs/utm-effectiveness.md @@ -3,7 +3,7 @@ **Чему учит:** приставка `declared_` в имени колонки — предупреждение, а не украшение. Она говорит: это заявка клиента, а не деньги отчетности. Урок здесь дает конфликт: суммы не сойдутся, и раскапывать почему предстоит -самому. +самостоятельно. Правило стенда: **поведение и атрибуцию считаем по трекеру, деньги — по бэкенду**. Эта витрина — единственное разрешенное исключение: атрибуции по @@ -88,21 +88,21 @@ UTM у бэкенда нет, метки живут только в кликст ## Ключ шардирования -Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по +Ваше решение по [правилу донора](../architecture/storage.md), «Раскладка по шардам». Зерно здесь составное, из трех колонок, и решение «взять хеш от -одной из них» надо защищать отдельно: назови, какая доля строк придется на +одной из них» надо защищать отдельно: назовите, какая доля строк придется на самое частое значение выбранной колонки. ## Конфликт, ради которого витрина и написана -Собери за один и тот же день две суммы: `sum(declared_revenue)` из своей +Соберите за один и тот же день две суммы: `sum(declared_revenue)` из своей витрины и `sum(revenue)` из `dm.revenue_daily_v`. Они не сойдутся, и это верное поведение обеих витрин. Базы у двух сумм при этом сравнимые: `revenue_daily` складывает цену позиций на количество и ни скидки, ни доставки в себя не берет — ровно то, что видит клиент. Значит все расхождение до копейки объясняется тем, что случилось с -заказом, а не тем, как считали деньги. Ищи ответы среди таких причин: +заказом, а не тем, как считали деньги. Ищите ответы среди таких причин: - отмененный заказ из кликстрима никуда не девается: событие покупки случилось, а `revenue_daily` считает только оплаченные; @@ -113,7 +113,7 @@ UTM у бэкенда нет, метки живут только в кликст Как эти же расхождения выглядят на зерне заказа, показывает стендовая витрина `dm.purchase_vs_orders_v` с ее классами расхождений: это готовый -ответ на вопрос «а сколько именно и из-за чего». Запиши свой вывод — +ответ на вопрос «а сколько именно и из-за чего». Запишите свой вывод — в двух-трех фразах, своими словами. Проверять сходимость этих двух сумм не нужно и нельзя: они и не должны @@ -122,8 +122,8 @@ UTM у бэкенда нет, метки живут только в кликст ## Как проверить себя Деловой ключ — тройка `utm_source`, `utm_medium`, `utm_campaign` внутри -дня. Собери прямой агрегат из `dds.event_v` за день, соедини с -`dm.utm_effectiveness_v` полным внешним соединением и посчитай строки, где +дня. Соберите прямой агрегат из `dds.event_v` за день, соедините с +`dm.utm_effectiveness_v` полным внешним соединением и посчитайте строки, где ключа нет с одной из сторон или где расходится любая из пяти величин. Ноль — зеленый.