Этап 1. Загрузка тестовых данных и создание таблиц CREATE TABLE dmedvedev.core_dim_course ON CLUSTER clickhouse_cluster ( course_id UInt32, title String, category String ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dmedvedev/core_dim_course', '{replica}' ) ORDER BY course_id; CREATE TABLE dmedvedev.core_dim_lesson ON CLUSTER clickhouse_cluster ( lesson_id UInt32, title String, duration_min UInt32, course_id UInt32 ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dmedvedev/core_dim_lesson', '{replica}' ) ORDER BY lesson_id; CREATE TABLE dmedvedev.core_dim_user ON CLUSTER clickhouse_cluster( user_id UInt32, name String, age UInt32, email String, registration_date DateTime(3) ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dmedvedev/core_dim_user', '{replica}' ) ORDER BY user_id; CREATE TABLE dmedvedev.core_enrollments ON CLUSTER clickhouse_cluster( user_id UInt32, course_id UInt32, enrolled_at DateTime(3) ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dmedvedev/core_enrollments', '{replica}' ) ORDER BY user_id; CREATE TABLE dmedvedev.core_fact_lesson_views ON CLUSTER clickhouse_cluster ( user_id UInt32, course_id UInt32, lesson_id UInt32, viewed_at DateTime(3) ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dmedvedev/core_fact_lesson_views', '{replica}' ) ORDER BY user_id; --Этап 2. Задание 2. --СОЗДАЕМ МАТ ПРЕДСТАВЛЕНИЕ mv_lesson_popularity_summary --для начала создаем таблицу в кликхаусе, реплицируемую с движком ReplicatedAggregatingMergeTree CREATE TABLE dmedvedev.dm_lesson_popularity_summary ON CLUSTER clickhouse_cluster ( lesson_id UInt32, lesson_title String, course_id UInt32, course_title String, total_views SimpleAggregateFunction(sum, UInt64), unique_users AggregateFunction(uniq, UInt32), first_view SimpleAggregateFunction(min, DateTime), last_view SimpleAggregateFunction(max, DateTime) ) ENGINE = ReplicatedAggregatingMergeTree( '/clickhouse/tables/{shard}/dmedvedev/dm_lesson_popularity_summary', '{replica}') ORDER BY (lesson_id, lesson_title, course_id, course_title); --далее создаем мат представление, считывает новые данные, делает джойны и кладет данные в dm_lesson_popularity_summary -- CREATE MATERIALIZED VIEW dmedvedev.mv_lesson_popularity_summary ON CLUSTER clickhouse_cluster TO dmedvedev.dm_lesson_popularity_summary AS SELECT flv.lesson_id AS lesson_id, dl.title AS lesson_title, dc.course_id AS course_id, dc.title AS course_title, COUNT(*) AS total_views, uniqState(flv.user_id) AS unique_users, MIN(flv.viewed_at) AS first_view, MAX(flv.viewed_at) AS last_view FROM dmedvedev.core_fact_lesson_views AS flv LEFT JOIN dmedvedev.core_dim_lesson AS dl ON flv.lesson_id = dl.lesson_id LEFT JOIN dmedvedev.core_dim_course AS dc ON dl.course_id = dc.course_id GROUP BY lesson_id, lesson_title, course_id, course_title; -- помещаем данные в dm_lesson_popularity_summary INSERT INTO dmedvedev.dm_lesson_popularity_summary SELECT flv.lesson_id AS lesson_id, dl.title AS lesson_title, dc.course_id AS course_id, dc.title AS course_title, COUNT(*) AS total_views, uniqState(flv.user_id) AS unique_users, MIN(flv.viewed_at) AS first_view, MAX(flv.viewed_at) AS last_view FROM dmedvedev.core_fact_lesson_views AS flv LEFT JOIN dmedvedev.core_dim_lesson AS dl ON flv.lesson_id = dl.lesson_id LEFT JOIN dmedvedev.core_dim_course AS dc ON dl.course_id = dc.course_id GROUP BY lesson_id, lesson_title, course_id, course_title; ЗАПРОС (ТОП 5 самых просматриваемых уроков уникальными пользователями): SELECT lesson_id, lesson_title, total_views, uniqMerge(unique_users) AS unique_users FROM dmedvedev.dm_lesson_popularity_summary dlps GROUP BY lesson_id, lesson_title, course_id, course_title, total_views, first_view, last_view ORDER BY unique_users DESC LIMIT 5; --Задание 3. СОЗДАЕМ МАТ ПРЕДСТАВЛЕНИЕ mv_inactive_users_summary --для начала создаем таблицу в кликхаусе, реплицируемую с движком ReplicatedAggregatingMergeTree CREATE TABLE dmedvedev.dm_inactive_users_summary ON CLUSTER clickhouse_cluster ( user_id UInt32, name String, email String, age UInt32, registrated_courses_count AggregateFunction(uniqExact, UInt32), registration_date DateTime ) ENGINE = ReplicatedAggregatingMergeTree( '/clickhouse/tables/{shard}/dmedvedev/dm_inactive_users_summary', '{replica}' ) ORDER BY (user_id, name, email, age, registration_date); --создаем само мат представление, которое перехватывает данные из таблицы фактов и помещает в кликхаус dmedvedev.dm_inactive_users_summary CREATE MATERIALIZED VIEW dmedvedev.mv_inactive_users_summary ON CLUSTER clickhouse_cluster TO dmedvedev.dm_inactive_users_summary AS SELECT ce.user_id, cdu.name, cdu.email, cdu.age, uniqExactState(ce.course_id) AS registrated_courses_count, cdu.registration_date FROM core_enrollments ce LEFT JOIN core_dim_user cdu ON ce.user_id = cdu.user_id GROUP BY ce.user_id, cdu.name, cdu.email, cdu.age, cdu.registration_date; -- помещаем данные в dm_inactive_users_summary INSERT INTO dmedvedev.dm_inactive_users_summary SELECT ce.user_id, cdu.name, cdu.email, cdu.age, uniqExactState(ce.course_id) AS registrated_courses_count, cdu.registration_date FROM core_enrollments ce LEFT JOIN core_dim_user cdu ON ce.user_id = cdu.user_id GROUP BY ce.user_id, cdu.name, cdu.email, cdu.age, cdu.registration_date; --читаем данные из dm_inactive_users_summary SELECT user_id, name, email, age, uniqExactMerge(registrated_courses_count) AS registrated_courses_count, registration_date FROM dmedvedev.dm_inactive_users_summary GROUP BY user_id, name, email, age, registration_date; ЗАПРОС (ученики зарегистрировавшиеся только на 1 курс): SELECT user_id, name, email, age, uniqExactMerge(registrated_courses_count) AS registrated_courses_count, registration_date FROM dmedvedev.dm_inactive_users_summary GROUP BY user_id, name, email, age, registration_date HAVING registrated_courses_count = 1; --Задание 4. создаем витрину course_completion_rate --для начала создаем таблицу в кликхаус с движком CREATE TABLE dmedvedev.dm_course_completion_rate ON CLUSTER clickhouse_cluster ( user_id UInt32, name String, course_id UInt32, course_title String, lessons_in_course SimpleAggregateFunction(max, UInt32), lessons_viewed AggregateFunction(uniqExact, UInt32) ) ENGINE = ReplicatedAggregatingMergeTree( '/clickhouse/tables/{shard}/dmedvedev/dm_course_completion_rate', '{replica}' ) ORDER BY (user_id, course_id, name, course_title); --далее создаем мат представление CREATE MATERIALIZED VIEW dmedvedev.mv_course_completion_rate ON CLUSTER clickhouse_cluster TO dmedvedev.dm_course_completion_rate AS SELECT cflv.user_id AS user_id, cdu.name AS name, cdl.course_id AS course_id, cdc.title AS course_title, max(count_lesson_in_course.total_leeson) AS lessons_in_course, uniqExactState(cflv.lesson_id) AS lessons_viewed FROM core_fact_lesson_views cflv LEFT JOIN core_dim_user cdu ON cflv.user_id = cdu.user_id LEFT JOIN core_dim_lesson cdl ON cflv.lesson_id = cdl.lesson_id LEFT JOIN core_dim_course cdc ON cdl.course_id = cdc.course_id LEFT JOIN ( SELECT course_id, uniqExact(lesson_id) AS total_leeson FROM dmedvedev.core_dim_lesson cdm GROUP BY course_id ) AS count_lesson_in_course ON count_lesson_in_course.course_id = cdl.course_id GROUP BY cflv.user_id, cdl.course_id, cdu.name, cdc.title; --вставляем данные INSERT INTO dmedvedev.dm_course_completion_rate SELECT cflv.user_id AS user_id, cdu.name AS name, cdl.course_id AS course_id, cdc.title AS course_title, max(count_lesson_in_course.total_leeson) AS lessons_in_course, uniqExactState(cflv.lesson_id) AS lessons_viewed FROM core_fact_lesson_views cflv LEFT JOIN core_dim_user cdu ON cflv.user_id = cdu.user_id LEFT JOIN core_dim_lesson cdl ON cflv.lesson_id = cdl.lesson_id LEFT JOIN core_dim_course cdc ON cdl.course_id = cdc.course_id LEFT JOIN ( SELECT course_id, uniqExact(lesson_id) AS total_leeson FROM dmedvedev.core_dim_lesson cdm GROUP BY course_id ) AS count_lesson_in_course ON count_lesson_in_course.course_id = cdl.course_id GROUP BY cflv.user_id, cdl.course_id, cdu.name, cdc.title; ЗАПРОС: ученики просмотревшие какой-либо курс менее чем на половину SELECT user_id, name, course_id, course_title, max(lessons_in_course) AS total_lessons, -- Объединяем просмотры: uniqExactMerge(lessons_viewed) AS viewed_lessons, IF(total_lessons > 0, CAST(viewed_lessons AS Float64) / total_lessons, 0.0) AS completion_rate FROM dmedvedev.dm_course_completion_rate GROUP BY user_id, name, course_id, course_title HAVING completion_rate <= 0.5 ORDER BY user_id, course_id; --ЭТАП 4. Тестирование и валидация В ДАННОМ ЗАДАНИИ Я НЕ ПРОИЗВОДИЛ СРАВНЕНИЕ ТАБЛИЦ ИЗ ГП И КХ, ТАК КАК ТАКБЛИЦЫ С ОДИНАКОВЫМИ НАЗВАНИЯМИ ПРОСТО НАПРОСТО СЧИТАЮТ РАЗНЫЕ ПОКАЗАТЕЛИ. ПОЧЕМУ ТАК ПРОИЗОШЛО? ПОТОМУ ЧТО В ЗАДАНИИ КХ ДАНЫ СОВСЕМ ДРУГИЕ ПОКАЗАЛИТЕ, ПОЭТОМУ ТАБЛИЦЫ РАЗЛИЧАЮТСЯ ПО НАПОЛНЕНИЮ. ЧТО Я ЗАМЕТИЛ ПРИ СОЗДАНИИ МАТ ПРЕДСТАВЛЕНИЙ В КХ? ТО ЧТО В ГП МАТ ПРЕДСТАВЛЕНИЯ ЭТО ОТДЕЛЬНАЯ ТАБЛИЦА, КОТОРАЯ ХРАНИТ ОПРЕДЕЛЕННЫЙ РЕЗУЛЬТАТ, ОНА НЕ ЯВЛЕТСЯ ДИНАМИЧЕСКОЙ, ЕЕ НЕОБХОДИМО ОБНОВЛЯТЬ ПРИ ПОСТУПЛЕНИИ НОВЫХ ДАННЫХ, СООТВЕТСТВЕННО ВЕСЬ АНАЛИЗ ДАННЫХ ГП ДЕЛАЕТ ПО ВСЕМ ДАННЫМ ЗАПРОСА. КХ В ОТЛИЧИЕ ОТ ГП РАБОТАЕТ ИНАЧЕ, ОН ПОДХВАТЫВАЕТ ЛИШЬ НОВЫЕ ДАННЫЕ, ПРИЧЕМ ЭТИ НОВЫЕ ДАННЫЕ МОГУТ ПОСТУПАТЬ ТОЛЬКО ИЗ ТАБЛИЦЫ ФАКТОВ, ЕСЛИ ОНИ ПОСТУПЯТ ИЗ ТАБЛИЦ ИЗМЕРЕНИЙ, ТО НИКАКИХ РАСЧЕТОМ КХ ДЕЛАТЬ НЕ БУДЕТ. ТАКЖЕ СТОИТ ОТМЕТИТЬ, ЧТО В КХ ПОЯВЛЯЕТСЯ ЦЕЛЕВАЯ ТАБЛИЦА, В КОТОРУЮ МАТ ПРЕДСТАВЛЕНИЕ И ПРОИЗВОДИТ ЗАПИСЬ ДАННЫХ И ПОТОМ В ЗАПРОСЕ МЫ ОБРАЩАЕМСЯ ИМЕННО К ЦЕЛЕВОЙ ТАБЛИЦЕ, А НЕ К МАТ ПРЕДСТАВЛЕНИЮ (МАТ ПРЕДСТАВЛЕНИЕ ЭТО ВСЕГО ЛИШЬ ТРИГГЕР, КОТОРЫЕ УЛАВЛИВАЕТ НОВЫЕ ДАННЫЕ ИЗ ТАБЛИЦЫ ФАКТОВ).