From beef2f7d91afd8103bc863986188eb513f88f0f4 Mon Sep 17 00:00:00 2001 From: Kap Date: Wed, 26 Nov 2025 23:53:20 +0600 Subject: [PATCH] pet project --- migration/course_migration.sql | 4 ++ migration/enrollments_migration.sql | 6 +++ migration/fact_lessons_view.sql | 7 +++ migration/lessons_migration.sql | 6 +++ migration/users_migration.sql | 6 +++ queries/analize.sql | 47 +++++++++++++++++++ queries/course_completion_rate.sql | 59 +++++++++++++++++++++++ queries/inactive_users_summary.sql | 46 ++++++++++++++++++ queries/lesson_popularity_summary.sql | 49 ++++++++++++++++++++ schema/tables.sql | 67 +++++++++++++++++++++++++++ 10 files changed, 297 insertions(+) create mode 100644 migration/course_migration.sql create mode 100644 migration/enrollments_migration.sql create mode 100644 migration/fact_lessons_view.sql create mode 100644 migration/lessons_migration.sql create mode 100644 migration/users_migration.sql create mode 100644 queries/analize.sql create mode 100644 queries/course_completion_rate.sql create mode 100644 queries/inactive_users_summary.sql create mode 100644 queries/lesson_popularity_summary.sql create mode 100644 schema/tables.sql diff --git a/migration/course_migration.sql b/migration/course_migration.sql new file mode 100644 index 0000000..bb220da --- /dev/null +++ b/migration/course_migration.sql @@ -0,0 +1,4 @@ +INSERT INTO pet_project.courses (course_id, title, category, created_at) VALUES +(1, 'SQL для начинающих', 'data', '2023-01-01 00:00:00'), +(2, 'Python для анализа данных', 'programming', '2023-01-15 00:00:00'), +(3, 'BI с нуля', 'business', '2023-03-01 00:00:00'); \ No newline at end of file diff --git a/migration/enrollments_migration.sql b/migration/enrollments_migration.sql new file mode 100644 index 0000000..5fc6f9b --- /dev/null +++ b/migration/enrollments_migration.sql @@ -0,0 +1,6 @@ +INSERT INTO pet_project.enrollments (id, user_id, course_id, enrolled_at) VALUES +(1, 1, 1, '2023-01-15'), +(2, 1, 2, '2023-02-01'), +(3, 2, 1, '2023-01-20'), +(4, 3, 2, '2023-03-05'), +(5, 4, 3, '2023-04-01'); \ No newline at end of file diff --git a/migration/fact_lessons_view.sql b/migration/fact_lessons_view.sql new file mode 100644 index 0000000..46038bf --- /dev/null +++ b/migration/fact_lessons_view.sql @@ -0,0 +1,7 @@ +INSERT INTO pet_project.fact_lesson_views (id, user_id, lesson_id, viewed_at) VALUES +(1, 1, 1, '2023-01-16 10:00:00'), +(2, 1, 2, '2023-01-16 10:15:00'), +(3, 2, 1, '2023-01-21 09:00:00'), +(4, 3, 3, '2023-03-06 12:00:00'), +(5, 4, 4, '2023-04-02 10:00:00'), +(6, 4, 5, '2023-04-03 10:00:00'); \ No newline at end of file diff --git a/migration/lessons_migration.sql b/migration/lessons_migration.sql new file mode 100644 index 0000000..85dec32 --- /dev/null +++ b/migration/lessons_migration.sql @@ -0,0 +1,6 @@ +INSERT INTO pet_project.lessons (lesson_id, course_id, title, duration_min) VALUES +(1, 1, 'SELECT и FROM', 10), +(2, 1, 'JOIN', 15), +(3, 2, 'Pandas', 20), +(4, 3, 'Основы BI', 12), +(5, 3, 'Метрики и дашборды', 18); \ No newline at end of file diff --git a/migration/users_migration.sql b/migration/users_migration.sql new file mode 100644 index 0000000..16f9c3a --- /dev/null +++ b/migration/users_migration.sql @@ -0,0 +1,6 @@ +INSERT INTO pet_project.users (user_id, name, age, email, registration_date) VALUES +(1, 'Alice', 25, 'alice@mail.com', '2023-01-10 00:00:00'), +(2, 'Bob', 30, 'bob@gmail.com', '2023-02-05 00:00:00'), +(3, 'Charlie', 22, 'charlie@mail.com', '2023-02-20 00:00:00'), +(4, 'Diana', 28, 'diana@mail.com', '2023-03-01 00:00:00'), +(5, 'Ethan', 35, 'ethan@gmail.com', '2023-03-10 00:00:00'); \ No newline at end of file diff --git a/queries/analize.sql b/queries/analize.sql new file mode 100644 index 0000000..ccba937 --- /dev/null +++ b/queries/analize.sql @@ -0,0 +1,47 @@ +-- ### ✅ **Задание 5: Напиши SQL-запросы к витринам** +-- **Напиши свои вариации для практики.** +-- 📌 *Ниже — примеры.* +-- ### Топ-5 самых просматриваемых уроков +-- SELECT +-- lesson_title, +-- course_title, +-- total_views +-- FROM lesson_popularity_summary +-- ORDER BY total_views DESC +-- LIMIT 5; +SELECT * +FROM ( + SELECT * FROM pet_project.mv_lesson_popularity_summary + ) AS top_lessons +LIMIT 5 +; + +WITH selected_top AS (SELECT * + FROM pet_project.mv_lesson_popularity_summary) +SELECT * +FROM selected_top +LIMIT 5 +; + + +-- ### Неактивные пользователи, записавшиеся на курсы +-- SELECT +-- name, +-- email, +-- registered_courses_count +-- FROM inactive_users_summary +-- WHERE registered_courses_count > 0 +-- ORDER BY registration_date DESC; + +-- дальше по аналогии как ВЫШЕ, подзапрос или CTE + +-- ### Курсы с самым высоким процентом завершения +-- SELECT +-- course_title, +-- AVG(completion_rate) AS avg_completion +-- FROM course_completion_rate +-- GROUP BY course_title +-- ORDER BY avg_completion DESC +-- LIMIT 5; + +-- дальше по аналогии как ВЫШЕ, подзапрос или CTE diff --git a/queries/course_completion_rate.sql b/queries/course_completion_rate.sql new file mode 100644 index 0000000..7ebdca1 --- /dev/null +++ b/queries/course_completion_rate.sql @@ -0,0 +1,59 @@ +-- ### ✅ **Задание 4: Создай витрину `course_completion_rate`** +-- +-- **Цель:** проанализировать, насколько хорошо пользователи проходят курсы. +-- +-- 📌 **Что нужно сделать:** +-- +-- - Сформируй таблицу, где строка — это пользователь + курс. +-- - Для каждой пары нужно рассчитать: +-- - Общее число уроков в курсе +-- - Сколько из них просмотрел конкретный пользователь +-- - Коэффициент завершения (`lessons_viewed / lessons_in_course`) +-- +-- **Как использовать метрику:** +-- +-- Можно анализировать, какие курсы чаще всего "бросают", какие хорошо проходят, где стоит улучшить структуру или добавить мотивационные механики (геймификация, рассылки и т.п.). +WITH course_lesson AS (SELECT course_id, count() AS total_lessons + FROM pet_project.lessons + GROUP BY course_id) +SELECT concat(u.name, ', ', c.title) AS user_course, + any(cl.total_lessons) AS lessons_in_course, + count(DISTINCT flv.lesson_id) AS lessons_viewed, + round(count(DISTINCT flv.lesson_id) / any(cl.total_lessons), 2) AS coef +FROM pet_project.users u + JOIN pet_project.fact_lesson_views flv ON flv.user_id = u.user_id + JOIN pet_project.lessons l ON l.lesson_id = flv.lesson_id + JOIN pet_project.courses c ON c.course_id = l.course_id + JOIN course_lesson cl ON cl.course_id = l.course_id +GROUP BY u.name, c.title +ORDER BY user_course +; + +-- создание mat view +CREATE MATERIALIZED VIEW pet_project.mv_user_course_progress + ENGINE = SummingMergeTree(lessons_viewed) + ORDER BY (user_id, course_id) + POPULATE +AS +SELECT flv.user_id AS user_id, + l.course_id AS course_id, + any(u.name) AS user_name, + any(c.title) AS course_title, + any(cl.total_lessons) AS lessons_in_course, + count(DISTINCT flv.lesson_id) AS lessons_viewed, + round(count(DISTINCT flv.lesson_id) / any(cl.total_lessons), 2) AS coef +FROM pet_project.fact_lesson_views flv + JOIN pet_project.users u ON u.user_id = flv.user_id + JOIN pet_project.lessons l ON l.lesson_id = flv.lesson_id + JOIN pet_project.courses c ON c.course_id = l.course_id + JOIN ( + SELECT course_id, count() AS total_lessons + FROM pet_project.lessons + GROUP BY course_id + ) cl ON cl.course_id = l.course_id +GROUP BY flv.user_id, l.course_id +; + +SELECT * +FROM pet_project.mv_user_course_progress +; diff --git a/queries/inactive_users_summary.sql b/queries/inactive_users_summary.sql new file mode 100644 index 0000000..6d9a086 --- /dev/null +++ b/queries/inactive_users_summary.sql @@ -0,0 +1,46 @@ +-- ### ✅ **Задание 3: Создай витрину `inactive_users_summary`** +-- +-- **Цель:** выявить пользователей, которые **зарегистрировались, но ничего не посмотрели**. +-- +-- 📌 **Что нужно сделать:** +-- +-- - Подготовь таблицу, где каждая строка — один такой "неактивный" пользователь. +-- - Для каждого пользователя нужны: +-- - ID, имя, email, возраст +-- - Количество курсов, на которые он записался +-- - Дата регистрации +-- **Где пригодится:** +-- +-- Эта витрина может использоваться для продуктовой аналитики, триггерных рассылок (например, напоминаний), анализа оттока, повышения вовлечённости. +SELECT u.user_id AS user_id, + any(u.name) AS user_name, + any(u.email) AS email, + any(u.age) AS age, + count() course_count, + any(u.registration_date) AS registration_date +FROM pet_project.users u + LEFT JOIN pet_project.enrollments e USING (user_id) +GROUP BY u.user_id +ORDER BY u.user_id +; + +-- Создание вьюшки +CREATE MATERIALIZED VIEW pet_project.mv_inactive_users_summary +REFRESH every 1 day +ENGINE SummingMergeTree() + ORDER BY (user_id) + AS +SELECT u.user_id AS user_id, + any(u.name) AS user_name, + any(u.email) AS email, + any(u.age) AS age, + count() course_count, + any(u.registration_date) AS registration_date +FROM pet_project.users u + LEFT JOIN pet_project.enrollments e USING (user_id) +GROUP BY u.user_id; +-- проверка +SELECT * FROM pet_project.mv_inactive_users_summary; + + + diff --git a/queries/lesson_popularity_summary.sql b/queries/lesson_popularity_summary.sql new file mode 100644 index 0000000..91f6cdb --- /dev/null +++ b/queries/lesson_popularity_summary.sql @@ -0,0 +1,49 @@ +-- ### ✅ **Задание 2: Подготовь структуру витрины `lesson_popularity_summary`** +-- +-- **Цель:** анализировать популярность каждого урока. +-- +-- 📌 **Что нужно сделать:** +-- +-- - Спроектируй таблицу, где каждая строка — один урок. +-- - Для каждого урока должны храниться: +-- - ID и название урока +-- - ID и название курса, к которому он относится +-- - Общее число просмотров +-- - Количество уникальных пользователей, посмотревших урок +-- - Дата первого и последнего просмотра +-- +-- **Зачем это нужно:** +-- +-- Эта витрина поможет выявлять наиболее популярные уроки и курсы, следить за актуальностью контента, понимать вовлечённость студентов по отдельным материалам. +SELECT l.lesson_id AS lesson_id, + l.title AS lesson_name, + c.course_id AS course_id, + c.title AS course_name, + countOrNull() AS total_view, + uniqOrNull(flv.user_id) AS unique_users, + min(flv.viewed_at) AS first_date, + max(flv.viewed_at) AS last_date +FROM pet_project.lessons l + LEFT JOIN pet_project.courses c ON c.course_id = l.course_id + LEFT JOIN pet_project.fact_lesson_views flv ON flv.lesson_id = l.lesson_id +GROUP BY l.lesson_id, l.title, c.course_id, c.title; + +-- Создание мат view +CREATE MATERIALIZED VIEW pet_project.mv_lesson_popularity_summary + ENGINE = SummingMergeTree() + ORDER BY (lesson_id, course_id) + POPULATE +AS +SELECT l.lesson_id AS lesson_id, + any(l.title) AS lesson_name, + c.course_id AS course_id, + any(c.title) AS course_name, + count() AS total_views, + uniq(flv.user_id) AS unique_users, + min(flv.viewed_at) AS first_date, + max(flv.viewed_at) AS last_date +FROM pet_project.lessons l + LEFT JOIN pet_project.courses c ON c.course_id = l.course_id + LEFT JOIN pet_project.fact_lesson_views flv ON flv.lesson_id = l.lesson_id +GROUP BY l.lesson_id, c.course_id; + diff --git a/schema/tables.sql b/schema/tables.sql new file mode 100644 index 0000000..1e1e218 --- /dev/null +++ b/schema/tables.sql @@ -0,0 +1,67 @@ +-- create users table +CREATE TABLE pet_project.users +( + user_id UInt32, + name String, + age UInt16, + email String, + registration_date DateTime +) + ENGINE MergeTree() + ORDER BY (user_id); + +-- create table course +DROP TABLE IF EXISTS pet_project.courses; +CREATE TABLE pet_project.courses +( + course_id UInt32, + title String, + category String, + created_at DateTime +) + ENGINE ReplacingMergeTree(created_at) + ORDER BY (course_id); + +-- create table dim_lesson +DROP TABLE pet_project.lessons; +CREATE TABLE pet_project.lessons +( + lesson_id UInt32, + title String, + duration_min UInt16, + course_id UInt32 +) + ENGINE MergeTree() + ORDER BY (lesson_id, course_id); + +-- create table fact_lesson_views +DROP TABLE pet_project.fact_lesson_views; +CREATE TABLE pet_project.fact_lesson_views +( + id UInt32, + user_id UInt32, + lesson_id UInt32, + course_id UInt32, + viewed_at Datetime +) + ENGINE MergeTree() + PARTITION BY toYYYYMM(viewed_at) + ORDER BY (course_id, lesson_id, user_id); +ALTER TABLE pet_project.fact_lesson_views +ADD PROJECTION user_lookup ( + SELECT * ORDER BY (user_id, lesson_id, course_id) +); + +ALTER TABLE pet_project.fact_lesson_views +MATERIALIZE PROJECTION user_lookup; + +-- create table enrollments +CREATE TABLE pet_project.enrollments +( + id UInt32, + user_id UInt32, + course_id UInt32, + enrolled_at DateTime +) + ENGINE MergeTree() + ORDER BY (user_id, course_id);