diff --git a/dmedvedev/10.1.Transactions.sql b/dmedvedev/10.1.Transactions.sql index 366d994..730d7f2 100644 --- a/dmedvedev/10.1.Transactions.sql +++ b/dmedvedev/10.1.Transactions.sql @@ -1,17 +1,22 @@ --Задание 1. Оформление заказа BEGIN; -INSERT INTO orders (user_id, order_date, status) -VALUES (10, '2025-01-15', 'paid'); + INSERT INTO orders + (user_id, order_date, status) + VALUES + (10, '2025-01-15', 'paid'); -INSERT INTO order_items (product_id, quantity, price_at_purchase) -VALUES(6, 1, 4800), (7, 1, 5200); + INSERT INTO order_items + (order_id, product_id, quantity, price_at_purchase) + VALUES + (LASTVAL(), 6, 1, 4800), + (LASTVAL(), 7, 1, 5200); -UPDATE products -SET in_stock = TRUE -WHERE id IN (6, 7); + UPDATE products + SET in_stock = FALSE + WHERE id IN (6, 7); -COMMIT; + COMMIT; /*Что в данных пойдёт не так, если часть шагов выполнится, а часть нет? В таком случае транзакция не будет закрыта. Я начал бегин, но произошла ошибка внутри транзакции и поэтому dbeaver дальше ничего выполнять не будет пока я не сделаю откат, либо не скажу завершить. diff --git a/dmedvedev/10.2.Blocks.sql b/dmedvedev/10.2.Blocks.sql index ba13206..0065c56 100644 --- a/dmedvedev/10.2.Blocks.sql +++ b/dmedvedev/10.2.Blocks.sql @@ -20,7 +20,7 @@ --просто прочитает данные. Боб лишь заблокирован для изменений. --что будет с UPDATE users SET country = 'FR' WHERE id = 2;. ---В таком случае БД увидит конфликт и одну из транзакций откатит с ошибкой , а вторую пропустит дальше. +--В таком случае БД зависнет (blocked) и будет ждать пока первая транзакция не исполнится. --Задание 4. Дедлок на двух пользователях --Объясни своими словами, почему возникает дедлок и что сделает база с одной из транзакций. diff --git a/dmedvedev/10.3.Optimization of select.sql b/dmedvedev/10.3.Optimization of select.sql index 4094305..33ec8c9 100644 --- a/dmedvedev/10.3.Optimization of select.sql +++ b/dmedvedev/10.3.Optimization of select.sql @@ -50,8 +50,8 @@ WHERE EXTRACT(YEAR FROM created_at) = 2026 SELECT id, name, age FROM users WHERE - created_at >= '2024-01-01' AND created_at < '2025-01-01' - AND country = 'RU' + created_at >= '2026-01-01' AND created_at < '2027-01-01' + AND country = 'россия' AND age > 25; Исправлено: выдача колонок "все" заменено на "определенные", логика в where с числами переработана, соответствие определенному году переработано и убрал функцию LOWER. diff --git a/dmedvedev/10.4.Explain.sql b/dmedvedev/10.4.Explain.sql index a240058..3ef5e4d 100644 --- a/dmedvedev/10.4.Explain.sql +++ b/dmedvedev/10.4.Explain.sql @@ -1,12 +1,18 @@ --Задача 1 — Читаем план + +EXPLAIN +SELECT name, email +FROM users +WHERE id = 1; + Какой тип сканирования использует база? --SeqScan - сканирование всей таблицы Что означает строка `Index Cond: (id = 1)`? --- У меня нет такой строки, есть "Filter: (id = 1)". Видимо это фильтрация по определенному значению, но в моем случае это должен быть индекс по столбцу id, но у меня индексы не проставлены, хорошо, что я хотя бы сейчас это уже понимаю. +-- Строка означает фильтрацию столбца по индексу, который есть в столбце id. Почему база использует индекс, а не читает всю таблицу? ---Потому что так быстрее и менее затратно. +--В моем случае БД читает всю таблицу, но обычно если индексы расставлены верно и таблица больших размеров, то БД лучше использовать индексы для повышенной производительности. ---------- --Задача 2 — Seq Scan vs Index Scan diff --git a/dmedvedev/11.4 Pet project Cohort_analysis.sql b/dmedvedev/11.4 Pet project Cohort_analysis.sql index a2b2b7d..d25e5fa 100644 --- a/dmedvedev/11.4 Pet project Cohort_analysis.sql +++ b/dmedvedev/11.4 Pet project Cohort_analysis.sql @@ -6,7 +6,8 @@ SELECT COUNT(DISTINCT pu.id) AS users_in_cohort, COUNT(DISTINCT pe.user_id) AS enrolled_users, COUNT(DISTINCT plv.user_id) AS active_users, - ROUND(COUNT(DISTINCT plv.user_id) * 100.0 / COUNT(DISTINCT pu.id), 2) || '%' AS retention_rate, + --ROUND(COUNT(DISTINCT plv.user_id) * 100.0 / COUNT(DISTINCT pu.id), 2) || '%' AS retention_rate, + ROUND(COUNT(pe.id) * 100.0 / COUNT(DISTINCT pu.id), 2) || '%' AS retention_rate, ROUND(AVG((SELECT COUNT(pe.id) FROM pet_enrollments pe)) / COUNT(DISTINCT pu.id), 2) AS avg_courses_per_user, (SELECT diff --git a/dmedvedev/11.5 Pet project Lesson_popularity_ranking.sql b/dmedvedev/11.5 Pet project Lesson_popularity_ranking.sql index 2f1235b..58f402e 100644 --- a/dmedvedev/11.5 Pet project Lesson_popularity_ranking.sql +++ b/dmedvedev/11.5 Pet project Lesson_popularity_ranking.sql @@ -5,8 +5,8 @@ SELECT pl.title AS lesson_title, pc.id AS course_id, pc.title AS course_title, - total_views_lesson.total_views AS total_views, - table_unique_viewers.unique_v AS unique_viewers, + COALESCE(total_views_lesson.total_views, 0) AS total_views, + COALESCE(table_unique_viewers.unique_v, 0) AS unique_viewers, ROUND(AVG(views_one_user_per_one_lesson.views_per_viewer), 2) AS avg_views_per_viewer, --также это можно было посчитать total_views/unique_viewers DENSE_RANK() OVER ( PARTITION BY pc.id diff --git a/dmedvedev/11.6 Pet project Conversion_funnel.sql b/dmedvedev/11.6 Pet project Conversion_funnel.sql index 1335254..cc048d6 100644 --- a/dmedvedev/11.6 Pet project Conversion_funnel.sql +++ b/dmedvedev/11.6 Pet project Conversion_funnel.sql @@ -1,152 +1,156 @@ -CREATE MATERIALIZED VIEW conversion_funnel AS +CREATE MATERIALIZED +VIEW conversion_funnel AS -- CTE какое кол-во человек записалось на курс -WITH +WITH -enrolled_users AS ( - SELECT - course_id, - COUNT(*) AS enrolled - FROM pet_enrollments pe - GROUP BY course_id -), + enrolled_users + AS + ( + SELECT + course_id, + user_id, + COUNT(*) OVER (PARTITION BY course_id) AS enrolled_count + FROM pet_enrollments pe + ), --- СТЕ на начавших смотреть курс -started_users AS ( - SELECT - pc.id AS course_id, - COUNT(DISTINCT plv.user_id) AS started - FROM pet_courses pc - LEFT JOIN pet_lessons pl - ON pc.id = pl.course_id - LEFT JOIN pet_lesson_views plv - ON plv.lesson_id = pl.id - GROUP BY pc.id -), + -- СТЕ на начавших смотреть курс + started_users + AS + ( + SELECT + eu.course_id, + COUNT(DISTINCT plv.user_id) AS started + FROM enrolled_users eu -- Базой берем записанных пользователей + LEFT JOIN pet_lessons pl + ON eu.course_id = pl.course_id + LEFT JOIN pet_lesson_views plv + ON plv.lesson_id = pl.id AND plv.user_id = eu.user_id + -- Проверяем, что смотрел именно записанный юзер + GROUP BY eu.course_id + ), ---СТЕ на кол-во просмотров курса и прогресс по курсу -user_progress AS ( - SELECT - pc.id AS course_id, - plv.user_id, - COUNT(DISTINCT lesson_id) AS count_views, - count_lesson_in_course.count_lesson_in_course AS count_lesson_in_course, - COUNT(DISTINCT lesson_id) * 1.0 / count_lesson_in_course AS progress - - FROM pet_courses pc - - LEFT JOIN pet_lessons pl - ON pl.course_id = pc.id - - LEFT JOIN pet_lesson_views plv - ON plv.lesson_id = pl.id - - LEFT JOIN ( - SELECT - pl.course_id, - COUNT(*) AS count_lesson_in_course - FROM pet_lessons pl - GROUP BY pl.course_id + --СТЕ на кол-во просмотров курса и прогресс по курсу + user_progress + AS + ( + SELECT + eu.course_id, + eu.user_id, + COUNT(DISTINCT plv.lesson_id) * 1.0 / count_lesson_in_course.count_lesson_in_course AS progress + FROM enrolled_users eu -- Базой берем записанных пользователей + LEFT JOIN pet_lessons pl + ON pl.course_id = eu.course_id + LEFT JOIN pet_lesson_views plv + ON plv.lesson_id = pl.id AND plv.user_id = eu.user_id -- Просмотры могут быть NULL, если записался, но не смотрел + LEFT JOIN ( + SELECT + pl.course_id, + COUNT(*) AS count_lesson_in_course + FROM pet_lessons pl + GROUP BY pl.course_id ) AS count_lesson_in_course - ON count_lesson_in_course.course_id = pc.id + ON count_lesson_in_course.course_id = eu.course_id + GROUP BY eu.course_id, eu.user_id, count_lesson_in_course.count_lesson_in_course + ), + main_table + AS + ( + SELECT + pc.id AS course_id, + pc.title AS course_title, + 'registered' AS stage_name, + 0 AS stage_order, + (SELECT COUNT(*) + FROM pet_users pu) AS users_count, + ROUND(100, 2) || '%' AS conversion_from_previous, + ROUND(100, 2) || '%' AS conversion_from_start - GROUP BY pc.id, plv.user_id, count_lesson_in_course - -), + FROM pet_courses pc -main_table AS ( - SELECT - pc.id AS course_id, - pc.title AS course_title, - 'registered' AS stage_name, - 0 AS stage_order, - (SELECT COUNT(*) FROM pet_users pu) AS users_count, - ROUND(100, 2) || '%' AS conversion_from_previous, - ROUND(100, 2) || '%' AS conversion_from_start - - FROM pet_courses pc - - UNION ALL - - SELECT - pc.id AS course_id, - pc.title AS course_title, - 'enrolled' AS stage_name, - 1 AS stage_order, - enrolled_users.enrolled, - ROUND(enrolled_users.enrolled * 100.0 / (SELECT COUNT(*) FROM pet_users pu), 2) || '%' AS conversion_from_previous, - ROUND(enrolled_users.enrolled * 100.0 / (SELECT COUNT(*) FROM pet_users pu), 2) || '%' AS conversion_from_start - - FROM pet_courses pc - LEFT JOIN enrolled_users - ON enrolled_users.course_id = pc.id - - - UNION ALL - - SELECT - pc.id AS course_id, - pc.title AS course_title, - 'started' AS stage_name, - 2 AS stage_order, - started_users.started, - ROUND(started_users.started * 100.0 / enrolled_users.enrolled, 2) || '%' AS conversion_from_previous, - ROUND(started_users.started * 100.0 / (SELECT COUNT(*) FROM pet_users pu), 2) || '%' AS conversion_from_start - - FROM pet_courses pc - LEFT JOIN started_users - ON started_users.course_id = pc.id - LEFT JOIN enrolled_users - ON enrolled_users.course_id = pc.id - - UNION ALL - - SELECT - pc.id AS course_id, - pc.title AS course_title, - 'mid_progress' AS stage_name, - 3 AS stage_order, - ( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 0.5 - ), - ROUND(( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 0.5) * 100.0 / started_users.started, 2) || '%' AS conversion_from_previous, - ROUND(( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 0.5) * 100.0 / (SELECT COUNT(*) FROM pet_users pu), 2) || '%' AS conversion_from_start - - FROM pet_courses pc - LEFT JOIN started_users - ON started_users.course_id = pc.id - UNION ALL - - SELECT - pc.id AS course_id, - pc.title AS course_title, - 'completed' AS stage_name, - 4 AS stage_order, - ( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 1), - ROUND(( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 1) * 100.0 / - ( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 0.5), 2) || '%' AS conversion_from_previous, - ROUND(( SELECT COUNT(*) FROM user_progress - WHERE user_progress.course_id = pc.id AND user_progress.progress >= 1) * 100.0 / - (SELECT COUNT(*) FROM pet_users pu), 2) || '%' AS conversion_from_start - FROM pet_courses pc - - ORDER BY course_id, stage_order -) + + SELECT + pc.id AS course_id, + pc.title AS course_title, + 'enrolled' AS stage_name, + 1 AS stage_order, + COALESCE(eu.enrolled_count, 0), + ROUND(COALESCE(eu.enrolled_count, 0) * 100.0 / NULLIF((SELECT COUNT(*) FROM pet_users), 0), 2) || '%' AS conversion_from_previous, + ROUND(COALESCE(eu.enrolled_count, 0) * 100.0 / NULLIF((SELECT COUNT(*) FROM pet_users), 0), 2) || '%' AS conversion_from_start + FROM pet_courses pc + LEFT JOIN (SELECT course_id, MAX(enrolled_count) AS enrolled_count + FROM enrolled_users + GROUP BY course_id) eu + ON eu.course_id = pc.id + + + UNION ALL + + SELECT + pc.id AS course_id, + pc.title AS course_title, + 'started' AS stage_name, + 2 AS stage_order, + COALESCE(su.started, 0), + ROUND(COALESCE(su.started, 0) * 100.0 / NULLIF(eu.enrolled_count, 0), 2) || '%' AS conversion_from_previous, + ROUND(COALESCE(su.started, 0) * 100.0 / NULLIF((SELECT COUNT(*) FROM pet_users), 0), 2) || '%' AS conversion_from_start + FROM pet_courses pc + LEFT JOIN (SELECT course_id, MAX(enrolled_count) AS enrolled_count + FROM enrolled_users + GROUP BY course_id) eu + ON eu.course_id = pc.id + LEFT JOIN started_users su + ON su.course_id = pc.id + + UNION ALL + + SELECT + pc.id AS course_id, + pc.title AS course_title, + 'mid_progress' AS stage_name, + 3 AS stage_order, + (SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 0.5), + ROUND((SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 0.5) * 100.0 / NULLIF(su.started, 0), 2) || '%' AS conversion_from_previous, + ROUND((SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 0.5) * 100.0 / NULLIF((SELECT COUNT(*) FROM pet_users), 0), 2) || '%' AS conversion_from_start + FROM pet_courses pc + LEFT JOIN started_users su + ON su.course_id = pc.id + + UNION ALL + + SELECT + pc.id AS course_id, + pc.title AS course_title, + 'completed' AS stage_name, + 4 AS stage_order, + (SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 1), + ROUND((SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 1) * 100.0 / NULLIF((SELECT COUNT(*) FROM user_progress up WHERE up.course_id = pc.id AND up.progress >= 0.5), 0), 2) || '%' AS conversion_from_previous, + ROUND((SELECT COUNT(*) + FROM user_progress up + WHERE up.course_id = pc.id AND up.progress >= 1) * 100.0 / NULLIF((SELECT COUNT(*) FROM pet_users), 0), 2) || '%' AS conversion_from_start + FROM pet_courses pc + ) -- main select -SELECT +SELECT course_id, course_title, stage_name, users_count, conversion_from_previous, conversion_from_start - -FROM main_table; \ No newline at end of file + +FROM main_table +ORDER BY course_id, stage_order; \ No newline at end of file diff --git a/dmedvedev/11.7 Pet Project Additional tasks.sql b/dmedvedev/11.7 Pet Project Additional tasks.sql index ce70e6b..5233dc7 100644 --- a/dmedvedev/11.7 Pet Project Additional tasks.sql +++ b/dmedvedev/11.7 Pet Project Additional tasks.sql @@ -1,14 +1,24 @@ -- 1. Задание 1: Анализ drop-off точек -- Используя conversion_funnel, найди курсы с самым большим процентом отвала между этапами enrolled → started (WHERE stage_name IN (...)) +WITH + drop_off + AS + ( + SELECT + course_id, + stage_name, + users_count, + LAG(users_count) OVER (PARTITION BY course_id) AS prev + + FROM conversion_funnel + ) + SELECT course_id, - stage_name, - conversion_from_previous, - conversion_from_start - - -FROM conversion_funnel -WHERE stage_name IN ('started'); + ROUND(1 - users_count * 1.0 / NULLIF(prev, 0), 2) * 100 || '%' AS percent_of_drop +FROM drop_off +WHERE stage_name = 'started' +ORDER BY percent_of_drop DESC -- ОТВЕТ: Самые большие проценты отвала между enrolled и started замечены на курсе 1 и 3 (62 и 67% соответственно).