diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1075.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1075.JPG new file mode 100644 index 0000000..3048d86 Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1075.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1141.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1141.JPG new file mode 100644 index 0000000..77ad312 Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1141.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1251.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1251.JPG new file mode 100644 index 0000000..8aa0d39 Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1251.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1581.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1581.JPG new file mode 100644 index 0000000..e778079 Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1581.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1633.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1633.JPG new file mode 100644 index 0000000..8e8d9ba Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1633.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1729.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1729.JPG new file mode 100644 index 0000000..c5ae3cd Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_1729.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_2356.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_2356.JPG new file mode 100644 index 0000000..971158b Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_2356.JPG differ diff --git a/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_596.JPG b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_596.JPG new file mode 100644 index 0000000..72c7da8 Binary files /dev/null and b/avysokih/postgresql/agragation_leetcode_screens/leetcode_agr_596.JPG differ diff --git a/avysokih/postgresql/command order/command_order.sql b/avysokih/postgresql/command order/command_order.sql new file mode 100644 index 0000000..116fd41 --- /dev/null +++ b/avysokih/postgresql/command order/command_order.sql @@ -0,0 +1,53 @@ +/*Задача 1: Какой будет порядок выполнения у запросов? + +SELECT name, age +FROM users +WHERE is_active = true AND country = 'Canada' +ORDER BY age +LIMIT 3; + +*/ + +1. FROM users +2. WHERE is_active = true AND country = 'Canada' +3. SELECT name, age +4. ORDER BY age +5. LIMIT 3 + + /* +SELECT country, COUNT(*) AS active_users +FROM users +WHERE is_active = true +GROUP BY country +HAVING COUNT(*) > 1; + */ + + 1. FROM users + 2. WHERE is_active = true + 3. GROUP BY country + 4. HAVING COUNT(*) > 1 + 5. SELECT country, COUNT(*) AS active_users + + /* +SELECT country, AVG(age) AS avg_age +FROM users +GROUP BY country +HAVING COUNT(*) >= 3 AND AVG(age) > 25; + */ + + 1. FROM users + 2. GROUP BY country + 3. HAVING COUNT(*) >= 3 AND AVG(age) > 25 + 4. SELECT country, AVG(age) AS avg_age + + /* +SELECT name, age +FROM users +WHERE is_active = false AND age > 30 +ORDER BY name; + */ + + 1. FROM users + 2. WHERE is_active = false AND age > 30 + 3. SELECT name, age + 4. ORDER BY name \ No newline at end of file diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1174.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1174.JPG new file mode 100644 index 0000000..956b130 Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1174.JPG differ diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1327.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1327.JPG new file mode 100644 index 0000000..e20ccf0 Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1327.JPG differ diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_176.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_176.JPG new file mode 100644 index 0000000..ef3c1aa Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_176.JPG differ diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_196.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_196.JPG new file mode 100644 index 0000000..6fc440f Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_196.JPG differ diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1978.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1978.JPG new file mode 100644 index 0000000..9eef6da Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_1978.JPG differ diff --git a/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_602.JPG b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_602.JPG new file mode 100644 index 0000000..21239c9 Binary files /dev/null and b/avysokih/postgresql/cte_leetcode_screens/leetcode_cte_602.JPG differ diff --git a/avysokih/postgresql/filtration tasks/filtr_code.sql b/avysokih/postgresql/filtration tasks/filtr_code.sql new file mode 100644 index 0000000..ad69680 --- /dev/null +++ b/avysokih/postgresql/filtration tasks/filtr_code.sql @@ -0,0 +1,104 @@ +-- Создание и заполнение таблицы + +create table products_extended( +id INT primary key, +name TEXT, +category TEXT, +price DECIMAL, +in_stock BOOLEAN, +email_support TEXT +); + +INSERT INTO products_extended (id, name, category, price, in_stock, email_support) VALUES +(1, 'iPhone 14', 'smartphone', 799, TRUE, 'iphone@apple.com'), +(2, 'MacBook Air', 'laptop', 1199, FALSE, NULL), +(3, 'Galaxy S22', 'smartphone', 699, TRUE, 'galaxy@support.com'), +(4, 'ThinkPad X1', 'laptop', 1399, TRUE, 'thinkpad@lenovo.com'), +(5, 'iPad Pro', 'tablet', 999, FALSE, 'ipad@apple.com'), +(6, 'Kindle Paperwhite', 'ebook_reader', 149, TRUE, NULL), +(7, 'Mi Band 7', 'smartwatch', 49, TRUE, 'miband@xiaomi.com'), +(8, 'Surface Pro', 'tablet', 1299, TRUE, 'surface@microsoft.com'), +(9, 'Honor Pad X9', 'tablet', 219, TRUE, 'honor@support.com'), +(10, 'Dell XPS 13', 'laptop', 1099, FALSE, 'dell@support.com'); + +/* +Задача 1: +Выведи все товары, которые: +- в наличии +- НЕ относятся к `smartwatch` и `ebook_reader` +- стоят от 300 до 1300 +И для них посчитай: +1. price с наценкой 10% +2. price с НДС 20% +3. Статус: если email_support IS NULL, подставь нет почты, иначе — сам email +*/ + + +select name, category, price, price * 1.1 as price_with_markup, price * 1.2 as price_with_vat, coalesce(email_support, 'нет почты') as status +from products_extended +where in_stock = true and category not in ('smartwatch', 'ebookreader') and price between 300 and 1300; + +/* +Задача 2: +Найти все товары: +1. У которых цена меньше 150 или больше 1200 +2. У которых** есть email поддержки +Для каждого выведи: +1. Название +2. Категорию +3. Email +4. Цену с прибавкой 25% +5. Отметку `premium` — если цена > 1000 +*/ + +select name, category, email_support, price * 1.25 as price_25, (price> 1000) as premium +from products_extended +where (price < 150 or price > 1200) and email_support is not null +order by premium; + +/* +Задача 3: +Найди все товары: +- где категория — `smartphone`, `laptop`, `tablet` +- название НЕ содержит `'i'` (чтобы исключить Apple) +- цена от 500 до 1300 +Выведи: +- `id`, `name`, `category`, `price` +- отсортируй по `id` +*/ + +select id, name, category, price +from products_extended +where category in('smartphone', 'laptop', 'tablet') and name not like '%i%' and price between 500 and 1300 +order by id desc; + +/* +Задача 4: +Найди товары, у которых: +- email_support IS NULL +- они в наличии +- стоят менее 800 +Выведи: +- `name`, `category`, `price` +- добавь колонку `comment`: `'нет поддержки'` +*/ + +select name, category, price, coalesce (email_support, 'нет поддержки') as comment +from products_extended +where email_support is null and in_stock is not null and price < 800; + +/* +Задача 5: +Найди товары, у которых: +- название заканчивается на `'Pro'` ИЛИ содержит `'X'` внутри (например, `X1`, `XPS`) +- в наличии +- с ценой > 700 +Выведи: +- `name`, `category`, `price` +- флаг `premium` — `true`, если `price > 1000`, иначе `false` +*/ + +select name, category, price, (price>1000) as premium +from products_extended +where (name like '%Pro%' or name like '%X%') and in_stock is true and price > 700 +order by price desc; \ No newline at end of file diff --git a/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1193.JPG b/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1193.JPG new file mode 100644 index 0000000..fafd8bf Binary files /dev/null and b/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1193.JPG differ diff --git a/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1211.JPG b/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1211.JPG new file mode 100644 index 0000000..3169731 Binary files /dev/null and b/avysokih/postgresql/functions_leetcode_screens/leetcode_func_1211.JPG differ diff --git a/avysokih/postgresql/join_leetcode_screens/leetcode_join_1068.JPG b/avysokih/postgresql/join_leetcode_screens/leetcode_join_1068.JPG new file mode 100644 index 0000000..020d556 Binary files /dev/null and b/avysokih/postgresql/join_leetcode_screens/leetcode_join_1068.JPG differ diff --git a/avysokih/postgresql/join_leetcode_screens/leetcode_join_1378.JPG b/avysokih/postgresql/join_leetcode_screens/leetcode_join_1378.JPG new file mode 100644 index 0000000..632ca2f Binary files /dev/null and b/avysokih/postgresql/join_leetcode_screens/leetcode_join_1378.JPG differ diff --git a/avysokih/postgresql/join_leetcode_screens/leetcode_join_577.JPG b/avysokih/postgresql/join_leetcode_screens/leetcode_join_577.JPG new file mode 100644 index 0000000..3ab2d52 Binary files /dev/null and b/avysokih/postgresql/join_leetcode_screens/leetcode_join_577.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_1.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_1.JPG new file mode 100644 index 0000000..3b3dc43 Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_1.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_2.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_2.JPG new file mode 100644 index 0000000..add8cae Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_2.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_3.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_3.JPG new file mode 100644 index 0000000..814cf79 Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_3.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_4.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_4.JPG new file mode 100644 index 0000000..3a734d6 Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_4.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_5.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_5.JPG new file mode 100644 index 0000000..4dd8e3e Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_5.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_fnt.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_fnt.JPG new file mode 100644 index 0000000..620cb65 Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_fnt.JPG differ diff --git a/avysokih/postgresql/select_leetcode_screens/leetcode_select_nbm.JPG b/avysokih/postgresql/select_leetcode_screens/leetcode_select_nbm.JPG new file mode 100644 index 0000000..eccca4e Binary files /dev/null and b/avysokih/postgresql/select_leetcode_screens/leetcode_select_nbm.JPG differ diff --git a/avysokih/postgresql/test_db.sql b/avysokih/postgresql/test_db.sql new file mode 100644 index 0000000..6c2929e --- /dev/null +++ b/avysokih/postgresql/test_db.sql @@ -0,0 +1 @@ +CREATE TABLE test_db;# \ No newline at end of file diff --git a/avysokih/postgresql/window functions/leetcode_180_1.JPG b/avysokih/postgresql/window functions/leetcode_180_1.JPG new file mode 100644 index 0000000..af73249 Binary files /dev/null and b/avysokih/postgresql/window functions/leetcode_180_1.JPG differ diff --git a/avysokih/postgresql/window functions/leetcode_180_2.JPG b/avysokih/postgresql/window functions/leetcode_180_2.JPG new file mode 100644 index 0000000..7b3587e Binary files /dev/null and b/avysokih/postgresql/window functions/leetcode_180_2.JPG differ diff --git a/avysokih/postgresql/window functions/win_func_tasks.sql b/avysokih/postgresql/window functions/win_func_tasks.sql new file mode 100644 index 0000000..87a4388 --- /dev/null +++ b/avysokih/postgresql/window functions/win_func_tasks.sql @@ -0,0 +1,86 @@ +/* +Задача 1: + +Для каждой категории показать 2 строки: +- Самый дорогой товар с email'ом +- Самый дорогой товар без email'а +*/ +with with_mail as ( +select category, name, price, email_support +from ( +select category, name, price, email_support, +row_number() over (partition by category order by price desc) as rn +from products_extended +where email_support is not null +) t +where rn = 1 +), +without_mail as ( +select category, name, price, email_support +from ( +select category, name, price, email_support, +row_number() over (partition by category order by price desc) as rn +from products_extended +where email_support is null +) t +where rn = 1 +) + +select * from with_mail +union all +select * from without_mail +order by category; + +/* +Задача 2: + +Для каждого товара показать: + +- `name` — название товара +- `category` — категория товара +- `price` — цена текущего товара +- `next_price` — цена следующего товара внутри той же категории, упорядоченного по цене +- `diff` — разница между ценой текущего товара и ценой следующего товара +(`price - next_price`) +*/ + +with t as( +select name, category, price, +lead(price) over (partition by category order by price) as next_price +from products_extended) +select name, category, price, next_price, +price - next_price as diff +from t order by category; + +/* +Задача 3: + +Показать `category`, `name`, `price`, `cumulative_sales` + +Где `cumulative_sales` — это сумма `price` **внутри категории** по мере возрастания цены +*/ + +select category, name, price, +sum(price) over (partition by category order by price) as cumulative_sales +from products_extended; + +/* +Задача 4: + +Вывести `category`, `name`, `price`, `avg_price`, `is_above_avg` + +Где `avg_price` — средняя цена по категории + +`is_above_avg = true`, если `price > avg_price` + +*/ + + +with t as( +select category, name, price, +round(avg(price) over (partition by category), 1) as avg_price +from products_extended) +select category, name, price, avg_price, +price > avg_price as is_above_avg +from t order by category; + diff --git a/ebelykh/postgresql/JOIN/JOIN.sql b/ebelykh/postgresql/JOIN/JOIN.sql new file mode 100644 index 0000000..ec94940 --- /dev/null +++ b/ebelykh/postgresql/JOIN/JOIN.sql @@ -0,0 +1,20 @@ +--Задача 1068 +SELECT p.product_name, s.year, s.price +FROM Sales s +JOIN Product p +ON s.product_id = p.product_id; + +--Задача 1378 +SELECT EmployeeUNI.unique_id, + Employees.name +FROM Employees +LEFT JOIN EmployeeUNI +ON Employees.id = EmployeeUNI.id; + +--Задача 577 +SELECT e.name, b.bonus +FROM Employee e +LEFT JOIN Bonus b +ON e.empId = b.empId +WHERE b.bonus < 1000 + OR b.bonus IS NULL; \ No newline at end of file diff --git a/ebelykh/postgresql/Оконные функции/task_1.png b/ebelykh/postgresql/Оконные функции/task_1.png new file mode 100644 index 0000000..c68655d Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_1.png differ diff --git a/ebelykh/postgresql/Оконные функции/task_180.1 .png b/ebelykh/postgresql/Оконные функции/task_180.1 .png new file mode 100644 index 0000000..35b3678 Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_180.1 .png differ diff --git a/ebelykh/postgresql/Оконные функции/task_180.2 .png b/ebelykh/postgresql/Оконные функции/task_180.2 .png new file mode 100644 index 0000000..6c07177 Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_180.2 .png differ diff --git a/ebelykh/postgresql/Оконные функции/task_2.png b/ebelykh/postgresql/Оконные функции/task_2.png new file mode 100644 index 0000000..26271a3 Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_2.png differ diff --git a/ebelykh/postgresql/Оконные функции/task_3.png b/ebelykh/postgresql/Оконные функции/task_3.png new file mode 100644 index 0000000..e9a5fa0 Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_3.png differ diff --git a/ebelykh/postgresql/Оконные функции/task_4.png b/ebelykh/postgresql/Оконные функции/task_4.png new file mode 100644 index 0000000..545e9a6 Binary files /dev/null and b/ebelykh/postgresql/Оконные функции/task_4.png differ diff --git a/ebelykh/postgresql/ПЭТ проект/Script.sql b/ebelykh/postgresql/ПЭТ проект/Script.sql new file mode 100644 index 0000000..25727d1 --- /dev/null +++ b/ebelykh/postgresql/ПЭТ проект/Script.sql @@ -0,0 +1,233 @@ +CREATE TABLE users ( + id INT PRIMARY KEY, + name TEXT, + age INT, + email TEXT, + registration_date DATE +); + +INSERT INTO users VALUES +(1, 'Alice', 25, 'alice@mail.com', '2023-01-10'), +(2, 'Bob', 30, 'bob@gmail.com', '2023-02-05'), +(3, 'Charlie', 22, 'charlie@mail.com', '2023-02-20'), +(4, 'Diana', 28, 'diana@mail.com', '2023-03-01'), +(5, 'Ethan', 35, 'ethan@gmail.com', '2023-03-10'); + +SELECT * FROM users ORDER BY id; + +CREATE TABLE courses ( + id INT PRIMARY KEY, + title TEXT, + category TEXT, + created_at DATE +); + +INSERT INTO courses VALUES +(1, 'SQL для начинающих', 'data', '2023-01-01'), +(2, 'Python для анализа данных', 'programming', '2023-01-15'), +(3, 'BI с нуля', 'business', '2023-03-01'); + + +SELECT * FROM courses ORDER BY id; + + +CREATE TABLE lessons ( + id INT PRIMARY KEY, + course_id INT, + title TEXT, + duration_min INT, + FOREIGN KEY (course_id) REFERENCES courses(id) +); + +INSERT INTO lessons VALUES +(1, 1, 'SELECT и FROM', 10), +(2, 1, 'JOIN', 15), +(3, 2, 'Pandas', 20), +(4, 3, 'Основы BI', 12), +(5, 3, 'Метрики и дашборды', 18); + +SELECT * FROM lessons ORDER BY id; + + +CREATE TABLE enrollments ( + id INT PRIMARY KEY, + user_id INT, + course_id INT, + enrolled_at DATE, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (course_id) REFERENCES courses(id) +); + +INSERT INTO enrollments 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'); + +SELECT * FROM enrollments ORDER BY id; + +CREATE TABLE lesson_views ( + id INT PRIMARY KEY, + user_id INT, + lesson_id INT, + viewed_at TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (lesson_id) REFERENCES lessons(id) +); + +INSERT INTO lesson_views 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'); + +SELECT * FROM lesson_views ORDER BY id; + +--Создаем materialized view c названием user_activity_summary + +DROP MATERIALIZED VIEW IF EXISTS user_activity_summary; + + +CREATE MATERIALIZED VIEW user_activity_summary AS +WITH +enr AS ( + SELECT + user_id, + COUNT(*) AS enrollment_count + FROM enrollments + GROUP BY user_id +), +views AS ( + SELECT + user_id, + COUNT(DISTINCT lesson_id) AS viewed_lessons_count, + MIN(viewed_at) AS first_view, + MAX(viewed_at) AS last_view + FROM lesson_views + GROUP BY user_id +), +possible AS ( + SELECT + e.user_id, + COUNT(l.id) AS possible_lessons + FROM enrollments e + JOIN lessons l ON l.course_id = e.course_id + GROUP BY e.user_id +) +SELECT + u.id AS user_id, + u.name, + enr.enrollment_count, + COALESCE(views.viewed_lessons_count, 0) AS viewed_lessons_count, + views.last_view, + views.first_view, + ROUND( + COALESCE(views.viewed_lessons_count, 0)::numeric + / possible.possible_lessons * 100, + 2 + ) || '%' AS engagement_rate +FROM users u +JOIN enr ON enr.user_id = u.id +JOIN possible ON possible.user_id = u.id +LEFT JOIN views ON views.user_id = u.id +ORDER BY u.id; + +--Проверка +SELECT * FROM user_activity_summary ORDER BY user_id; + + +--Создаем materialized view c названием course_summary + +DROP MATERIALIZED VIEW IF EXISTS course_summary; + + +CREATE MATERIALIZED VIEW course_summary AS +WITH +-- 1) сколько уроков в каждом курсе +lessons_cnt AS ( + SELECT + course_id, + COUNT(*) AS lessons_count + FROM lessons + GROUP BY course_id +), + +-- 2) сколько уникальных пользователей записано на курс +users_cnt AS ( + SELECT + course_id, + COUNT(DISTINCT user_id) AS unique_users + FROM enrollments + GROUP BY course_id +), + +-- 3) просмотры по курсу (через lessons -> course_id) +views_cnt AS ( + SELECT + l.course_id, + COUNT(lv.id) AS total_views, + MIN(lv.viewed_at) AS first_viewed_at, + MAX(lv.viewed_at) AS last_viewed_at + FROM lessons l + LEFT JOIN lesson_views lv ON lv.lesson_id = l.id + GROUP BY l.course_id +) + +SELECT + c.id AS course_id, + c.title AS course_title, + + COALESCE(u.unique_users, 0) AS unique_users, + COALESCE(ls.lessons_count, 0) AS lessons_count, + COALESCE(v.total_views, 0) AS total_views, + + ROUND( + COALESCE(v.total_views, 0)::numeric / NULLIF(COALESCE(u.unique_users, 0), 0), + 2 + ) AS avg_views_per_user, + + v.first_viewed_at, + v.last_viewed_at +FROM courses c +LEFT JOIN users_cnt u ON u.course_id = c.id +LEFT JOIN lessons_cnt ls ON ls.course_id = c.id +LEFT JOIN views_cnt v ON v.course_id = c.id +ORDER BY c.id; + +SELECT * FROM course_summary ORDER BY course_id; + +--Создаем materialized view c названием platform_summary + +DROP MATERIALIZED VIEW IF EXISTS platform_summary; + +CREATE MATERIALIZED VIEW platform_summary AS +SELECT + (SELECT COUNT(*) FROM users) AS total_users, + (SELECT COUNT(*) FROM courses) AS total_courses, + (SELECT COUNT(*) FROM lessons) AS total_lessons, + + (SELECT COUNT(DISTINCT user_id) FROM lesson_views) AS users_with_views, + + ROUND( + (SELECT COUNT(*) FROM lessons)::numeric + / (SELECT COUNT(*) FROM courses), + 2 + ) AS avg_lessons_per_course, + + ROUND( + (SELECT COUNT(*) FROM lesson_views)::numeric + / (SELECT COUNT(*) FROM lessons), + 2 + ) AS avg_views_per_lesson; + + SELECT * FROM platform_summary; + +--Доп. задание: EXPLAIN +EXPLAIN SELECT * FROM user_activity_summary; +EXPLAIN SELECT * FROM course_summary; + + + \ No newline at end of file diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_01174.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01174.png new file mode 100644 index 0000000..1591517 Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01174.png differ diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_01327.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01327.png new file mode 100644 index 0000000..a1da532 Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01327.png differ diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_0176.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0176.png new file mode 100644 index 0000000..d9d5cb5 Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0176.png differ diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_0196.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0196.png new file mode 100644 index 0000000..c46c7ad Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0196.png differ diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_01978.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01978.png new file mode 100644 index 0000000..24108d4 Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_01978.png differ diff --git a/ebelykh/postgresql/Подзапросы и СТЕ/Task_0602.png b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0602.png new file mode 100644 index 0000000..f9885ed Binary files /dev/null and b/ebelykh/postgresql/Подзапросы и СТЕ/Task_0602.png differ diff --git a/ebelykh/postgresql/агрегации и группировки/агрегации.sql b/ebelykh/postgresql/агрегации и группировки/агрегации.sql new file mode 100644 index 0000000..7d5bf7d --- /dev/null +++ b/ebelykh/postgresql/агрегации и группировки/агрегации.sql @@ -0,0 +1,72 @@ +--Задача 1 +В варианте А: WHERE фильтрует товары и убирает те, которые не в наличии, потом эти товары(строки)участвуют в группировке, в варианте В - HAVING фильтрует уже сгруппированные данные(категории) + +--Задача 2356 +SELECT teacher_id, + COUNT(DISTINCT subject_id) AS cnt +FROM teacher +GROUP BY teacher_id; + +--Задача 1581 +SELECT customer_id, + COUNT(v.visit_id) AS count_no_trans +FROM visits v +LEFT JOIN transactions t +ON v.visit_id = t.visit_id +WHERE t.transaction_id IS NULL +GROUP BY customer_id; + +--Задача 1141 +SELECT activity_date AS day, + COUNT(DISTINCT user_id) AS active_users +FROM Activity +WHERE activity_date BETWEEN '2019-06-28' AND '2019-07-27' +GROUP BY activity_date; + +--Задача 596 +SELECT class +FROM Courses +GROUP BY class +HAVING COUNT(student) >= 5; + +--Задача 1075 +SELECT project_id, + ROUND(AVG(experience_years), 2) AS average_years +FROM Project p +JOIN Employee e +ON p.employee_id = e.employee_id +GROUP BY project_id; + +--Задача 1729 +SELECT user_id, + COUNT (follower_id) AS followers_count +FROM followers +GROUP BY user_id +ORDER BY user_id; + +--Задача 1633 +SELECT + r.contest_id, + ROUND( + COUNT(r.user_id) * 100.0 / (SELECT COUNT(*) FROM Users), + 2 + ) AS percentage +FROM Register r +GROUP BY r.contest_id +ORDER BY percentage DESC, r.contest_id ASC; + +--Задача 1251 +SELECT + p.product_id, + COALESCE( + ROUND( + SUM(u.units * p.price) * 1.0 / NULLIF(SUM(u.units), 0), + 2 + ), + 0 + ) AS average_price +FROM Prices p +LEFT JOIN UnitsSold u + ON p.product_id = u.product_id + AND u.purchase_date BETWEEN p.start_date AND p.end_date +GROUP BY p.product_id; \ No newline at end of file diff --git a/ebelykh/postgresql/базовый SQL/Select.sql b/ebelykh/postgresql/базовый SQL/Select.sql new file mode 100644 index 0000000..2ccfb16 --- /dev/null +++ b/ebelykh/postgresql/базовый SQL/Select.sql @@ -0,0 +1,43 @@ +--Задача 1757 +SELECT product_id +FROM Products +WHERE low_fats = 'Y' AND recyclable = 'Y'; + +--Задача 584 +SELECT name +FROM Customer +WHERE referee_id!= 2 OR referee_id IS NULL; + +--Задача 595 +SELECT name, population, area +FROM World +WHERE area >= 3000000 + OR population >= 25000000; + +--Задача 1148 +SELECT DISTINCT author_id AS id +FROM Views +WHERE author_id = viewer_id +ORDER BY id; + +--Задача 1683 +SELECT tweet_id +FROM Tweets +WHERE CHAR_LENGTH(content) > 15; + +--Задача 620 +SELECT id, movie, description, rating +FROM Cinema +WHERE id % 2 = 1 + AND description != 'boring' +ORDER BY rating DESC; + +--Задача 1667 +SELECT + user_id, + CONCAT( + UPPER(SUBSTR(name, 1, 1)), + LOWER(SUBSTR(name, 2)) + ) AS name +FROM Users +ORDER BY user_id; diff --git a/ebelykh/postgresql/оконные функции/task_1.png b/ebelykh/postgresql/оконные функции/task_1.png new file mode 100644 index 0000000..c68655d Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_1.png differ diff --git a/ebelykh/postgresql/оконные функции/task_180.1 .png b/ebelykh/postgresql/оконные функции/task_180.1 .png new file mode 100644 index 0000000..35b3678 Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_180.1 .png differ diff --git a/ebelykh/postgresql/оконные функции/task_180.2 .png b/ebelykh/postgresql/оконные функции/task_180.2 .png new file mode 100644 index 0000000..6c07177 Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_180.2 .png differ diff --git a/ebelykh/postgresql/оконные функции/task_2.png b/ebelykh/postgresql/оконные функции/task_2.png new file mode 100644 index 0000000..26271a3 Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_2.png differ diff --git a/ebelykh/postgresql/оконные функции/task_3.png b/ebelykh/postgresql/оконные функции/task_3.png new file mode 100644 index 0000000..e9a5fa0 Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_3.png differ diff --git a/ebelykh/postgresql/оконные функции/task_4.png b/ebelykh/postgresql/оконные функции/task_4.png new file mode 100644 index 0000000..545e9a6 Binary files /dev/null and b/ebelykh/postgresql/оконные функции/task_4.png differ diff --git a/ebelykh/postgresql/основы SQL/Script.sql b/ebelykh/postgresql/основы SQL/Script.sql new file mode 100644 index 0000000..f698b99 --- /dev/null +++ b/ebelykh/postgresql/основы SQL/Script.sql @@ -0,0 +1,33 @@ +--Задача 1 + CREATE TABLE products_extended ( + id INT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + category VARCHAR(50), + price DECIMAL(6,2), + in_stock BOOLEAN DEFAULT true, + email_support VARCHAR +); + + +--Задача 2 Вариант с DEFAULT (in_stock не указываем) +INSERT INTO products_extended (id, name, category, price, email_support) +VALUES +(1, 'Mi Band 7', 'smartwatch', 49.00, 'miband@xiaomi.com'), +(2, 'Surface Pro', 'tablet', 1299.00, 'surface@microsoft.com'); + +--Задача 3 +ALTER TABLE products_extended +ADD COLUMN rating DECIMAL(3,2), +DROP COLUMN email_support; + +--Задача 4 +UPDATE products_extended +SET price = price * 1.1 +WHERE name = 'Mi Band 7'; + +--Задача 5(удаляет данные/таблицу) +TRUNCATE TABLE products_extended; +DROP TABLE products_extended; + + + diff --git a/ebelykh/postgresql/фильтрация/Script.sql b/ebelykh/postgresql/фильтрация/Script.sql new file mode 100644 index 0000000..c666713 --- /dev/null +++ b/ebelykh/postgresql/фильтрация/Script.sql @@ -0,0 +1,54 @@ +SELECT + name, + category, + price, + price * 1.10 AS price_with_markup, + price * 1.20 AS price_with_vat, + COALESCE(email_support, 'нет почты') AS status +FROM products_extended +WHERE in_stock = true + AND category NOT IN ('smartwatch', 'ebook_reader') + AND price BETWEEN 300 AND 1300; + +SELECT + name, + category, + email_support, + price * 1.25 AS price_25, + price > 1000 AS premium +FROM products_extended +WHERE (price < 150 OR price > 1200) + AND email_support IS NOT NULL; + +SELECT + id, + name, + category, + price +FROM products_extended +WHERE category IN ('smartphone', 'laptop', 'tablet') + AND name NOT LIKE '%i%' + AND price BETWEEN 500 AND 1300 +ORDER BY id; + +SELECT + name, + category, + price, + 'нет поддержки' AS comment +FROM products_extended +WHERE in_stock = true + AND email_support IS NULL + AND price < 800; + +SELECT + name, + category, + price, + price > 1000 AS premium +FROM products_extended +WHERE (name LIKE '%Pro' OR name LIKE '%X%') + AND in_stock = true + AND price > 700; + + diff --git a/ebelykh/postgresql/функции/функции.sql b/ebelykh/postgresql/функции/функции.sql new file mode 100644 index 0000000..eb1a659 --- /dev/null +++ b/ebelykh/postgresql/функции/функции.sql @@ -0,0 +1,20 @@ +--Задача 1211 +SELECT + query_name, + ROUND(AVG(rating * 1.0 / position), 2) AS quality, + ROUND(AVG(CASE WHEN rating < 3 THEN 1 ELSE 0 END) * 100,2) AS poor_query_percentage +FROM Queries +GROUP BY query_name; + +--Задача 1193 +SELECT + TO_CHAR(trans_date, 'YYYY-MM') AS month, + country, + COUNT(*) AS trans_count, + SUM(CASE WHEN state = 'approved' THEN 1 ELSE 0 END) AS approved_count, + SUM(amount) AS trans_total_amount, + SUM(CASE WHEN state = 'approved' THEN amount ELSE 0 END) AS approved_total_amount +FROM Transactions +GROUP BY + TO_CHAR(trans_date, 'YYYY-MM'), + country; \ No newline at end of file diff --git a/mdrozdov/clickhouse/array_map_nested.sql b/mdrozdov/clickhouse/array_map_nested.sql new file mode 100644 index 0000000..02959e9 --- /dev/null +++ b/mdrozdov/clickhouse/array_map_nested.sql @@ -0,0 +1,23 @@ +--task 1 + select + user_id, + indexOf(tags, 'promo') as promo_index, + arrayJoin(tags) + from events + where has(tags, 'promo') + + -- task 2 + select + user_id, + scores['math'] as math_score, + mapValues(scores) as all_values + from metrics + + -- task 3 + select + name, + contact.type as contact_type, + contact.value as contact_value + from users_nested + array join contacts as contact + where contact.type = 'email' \ No newline at end of file diff --git a/mdrozdov/clickhouse/data_types.sql b/mdrozdov/clickhouse/data_types.sql new file mode 100644 index 0000000..fc580c3 --- /dev/null +++ b/mdrozdov/clickhouse/data_types.sql @@ -0,0 +1,61 @@ +-- task 1 + CREATE TABLE raw_data ( + user_id String, + balance String, + created_at String + ) ENGINE = MergeTree ORDER BY tuple(); + + select + user_id, + toDecimal64OrNull(balance, 2) as balance, + toDateTime64OrNull(created_at, 3) as created_at + from raw_data + where + toDecimal64OrNull(balance, 2) is not null and + toDateTime64OrNull(created_at, 3) is not null + + -- task 2 (failed) + CREATE TABLE events ( + event_id String, + event_date String, -- форматы: "2023-01-01", "01.02.2023", "invalid" + event_time String -- форматы: "12:30:45", "25:99:99" + ); + + select + event_id, + toStartOfHour(toDateTimeOrNull(concatWithSeparator(' ', event_date, event_time))) + from events + where + toDateOrNull(event_date) is not null and toTimeOrNull(event_time) is not null + + -- task 3 + CREATE TABLE user_activity ( + user_id UInt32, + actions String -- JSON: ["login", "purchase", "logout"] + ); + SELECT + user_id, + actions_arr, + length(actions_arr) AS actions_count + FROM + ( + SELECT + user_id, + JSONExtract(actions, 'Array(String)') AS actions_arr + FROM user_activity + ) + WHERE has(actions_arr, 'purchase'); + + -- task 4 + CREATE TABLE sensor_data ( + sensor_id UInt32, + value Float64, + status Enum('ok' = 1, 'error' = 2) + ); + SELECT + toString(status) as status, + avg(value) as avg_value + from sensor_data + where + value is not null + group by status \ No newline at end of file diff --git a/mdrozdov/clickhouse/joins.sql b/mdrozdov/clickhouse/joins.sql new file mode 100644 index 0000000..4701740 --- /dev/null +++ b/mdrozdov/clickhouse/joins.sql @@ -0,0 +1,103 @@ +--task 1 + create table mdrozdov.products ( + product_id Int, + name String + ) + engine = MergeTree + order by product_id + + insert into mdrozdov.products values + (101, 'iPhone 14'), + (102, 'Galaxy S22'), + (103, 'Pixel 7') + + create table mdrozdov.prices ( + product_id Int, + price Int, + region String + ) + engine = MergeTree + order by product_id + + insert into mdrozdov.prices values + (101, 999, 'US'), + (101, 1049, 'EU'), + (102, 850, 'US'), + (102, 899, 'CA'), + (103, 799, 'JP') + + -- 1) + select + product_id, + name, + price, + region + from mdrozdov.products + any left join mdrozdov.prices + on mdrozdov.products.product_id = mdrozdov.prices.product_id + + -- 2) + create table mdrozdov.ticks + ON CLUSTER clickhouse_cluster ( + time DateTime, + symbol String, + price Decimal(10, 2) + ) + engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/ticks', + '{replica}' + ) + order by (time, symbol) + + insert into mdrozdov.ticks values + ('2025-07-17 10:00:00', 'AAPL', 190.5), + ('2025-07-17 10:01:00', 'AAPL', 191.2), + ('2025-07-17 10:02:30', 'AAPL', 192.0), + ('2025-07-17 10:05:00', 'GOOG', 2850.0) + + create table mdrozdov.orders + ON CLUSTER clickhouse_cluster ( + time DateTime, + symbol String, + size Int + ) + engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/orders', + '{replica}' + ) + order by (symbol, time) + + insert into mdrozdov.orders values + ('2025-07-17 10:00:30', 'AAPL', 50), + ('2025-07-17 10:02:00', 'AAPL', 75), + ('2025-07-17 10:03:00', 'AAPL', 20), + ('2025-07-17 10:04:00', 'GOOG', 15) + + select + o.time, + o.symbol, + t.price as last_price + from mdrozdov.orders o + asof join mdrozdov.ticks t + on o.symbol = t.symbol and o.time >= t.time + + -- task 3 + select + user_id, + name + from users + where user_id in ( + select user_id + from payments + where amount > 1000 + ) + + -- task 4 + select + bl.user_id, + u.name, + bl.event_time, + bl.event_type + from big_logs bl + any left join users u + using(user_id) \ No newline at end of file diff --git a/mdrozdov/clickhouse/merge_tree_family.sql b/mdrozdov/clickhouse/merge_tree_family.sql new file mode 100644 index 0000000..74dbfba --- /dev/null +++ b/mdrozdov/clickhouse/merge_tree_family.sql @@ -0,0 +1,64 @@ +--page_views table + create table page_views ( + user_id UInt64, + url String, + view_time DateTime + ) + Engine = MergeTree + Order by (user_id, view_time) + Partition by toYYYYMM(view_time) + + -- users table + create table users( + id UInt64, + name String, + updated_at DateTime + ) + ENGINE = ReplacingMergeTree(updated_at) + Order by id; + + -- region_sales table + create table mdrozdov.region_sales( + region String, + date Date, + sales UInt64 + ) + ENGINE = SummingMergeTree + Partition by toYYYYMM(date) + Order by (region, date) + + insert into mdrozdov.region_sales (region, date, sales) + values ('Russia', '2025-01-14', 30), ('Russia', '2025-01-16', 22), ('USA', '2025-01-14', 1400) + OPTIMIZE TABLE mdrozdov.region_sales FINAL + + -- user_metrics table + create table mdrozdov.user_metrics ( + region String, + avg_age_state AggregateFunction(avg, UInt8), + active_users_state AggregateFunction(uniq, UInt64) + ) + Engine = AggregatingMergeTree + Order by region + + INSERT INTO mdrozdov.user_metrics + SELECT + 'Moscow' AS region, + avgState(toUInt8(25)) AS avg_age_state, + uniqState(toUInt64(100)) AS active_users_state + UNION ALL + SELECT + 'Moscow', + avgState(toUInt8(30)), + uniqState(toUInt64(50)) + UNION ALL + SELECT + 'SPB', + avgState(toUInt8(20)), + uniqState(toUInt64(10)); + + SELECT + region, + avgMerge(avg_age_state) AS avg_age, + uniqMerge(active_users_state) AS active_users + FROM mdrozdov.user_metrics + GROUP BY region \ No newline at end of file diff --git a/mdrozdov/clickhouse/partitions.sql b/mdrozdov/clickhouse/partitions.sql new file mode 100644 index 0000000..817d95b --- /dev/null +++ b/mdrozdov/clickhouse/partitions.sql @@ -0,0 +1,98 @@ +-- task 1 +create table mdrozdov.sales2 +on CLUSTER clickhouse_cluster ( + id Int, + region String, + sale_date Date, + amount Decimal(8, 2) +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/sales2', + '{replica}' + ) +partition by toYYYYMM(sale_date) +order by id +-- удаление таблицы +-- drop table mdrozdov.sales +-- on CLUSTER clickhouse_cluster; не удаляет таблицу полностью, она остается в zookeeper +INSERT into mdrozdov.sales2 values +(1, 'RU', '2024-05-01', 100.0), +(2, 'RU', '2024-05-15', 120.0), --эти значения удалились +(3, 'US', '2024-06-01', 90.0), +(4, 'US', '2024-07-03', 150.0), +(5, 'DE', '2024-07-04', 160.0) +alter table mdrozdov.sales2 +drop partition '202405' +--2 значения удалились, которые хранились в соответствующей партиции + +-- task 2 +create table mdrozdov.events2 +on CLUSTER clickhouse_cluster ( + id Int, + event_type String, + event_date Date +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/events2', + '{replica}' + ) +partition by event_type +order by id + +INSERT into mdrozdov.events2 values +(1, 'login', '2024-06-01'), +(2, 'click', '2024-06-01'), +(3, 'click', '2024-06-02'), +(4, 'purchase', '2024-06-03'), +(5, 'login', '2024-06-04') + +alter table mdrozdov.events2 +drop partition 'click' + +-- task 3 +create table mdrozdov.logs +on CLUSTER clickhouse_cluster ( + id Int, + user_id Int, + log_time DateTime +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/logs', + '{replica}' + ) +partition by cityHash64(user_id) % 100 +order by id + +insert into mdrozdov.logs values +(1, 123, '2024-07-17 10:00:00'), +(2, 456, '2024-07-17 10:01:00'), +(3, 789, '2024-07-17 10:02:00') +--Что ты выиграешь с точки зрения баланса? +--равномерное распределение данных в разных партициях, лучшее управление обновлением и удалением данных +-- Почему такой тип PARTITION не ускорит SELECT * WHERE user_id = 456? +-- потому что clickhouse не умеет выполнять условия партиции, чтобы найти сразу ту в которой надо искать, +-- поэтому будет пробегать все 100, чтобы найти нужное, поэтому важно, чтобы таблица была отсортирована по user_id + +-- task 4 +create table mdrozdov.visits +on CLUSTER clickhouse_cluster ( + id Int, + region String, + visit_date Date +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/visits', + '{replica}' + ) +partition by (region, toYYYYMM(visit_date)) +order by (region, visit_date) + +insert into mdrozdov.visits values +(1, 'RU', '2024-06-01'), +(2, 'RU', '2024-06-15'), +(3, 'US', '2024-06-03'), +(4, 'US', '2024-07-01'), +(5, 'RU', '2024-07-05') + +alter table mdrozdov.visits +drop partition ('RU', 202407) -- эффективное удаление июльских данных по россии \ No newline at end of file diff --git a/mdrozdov/clickhouse/queries.sql b/mdrozdov/clickhouse/queries.sql new file mode 100644 index 0000000..e2d7afe --- /dev/null +++ b/mdrozdov/clickhouse/queries.sql @@ -0,0 +1,136 @@ +CREATE TABLE mdrozdov.users ( + user_id Int, + name String, + age Int, + country String, + is_active bool + ) + engine = MergeTree + order by user_id + + insert into mdrozdov.users values + (1, 'Ivan' ,30, 'RU', 1), + (2, 'Anna' ,25, 'RU', 1), + (3, 'Petr' ,45, 'RU', 0), + (4, 'Olga' ,35, 'RU', 1), + (5, 'John' ,55, 'US', 1), + (6, 'Alice' ,42, 'US', 0), + (7, 'Tom' ,38, 'US', 1), + (8, 'Lucas' ,28, 'BR', 1), + (9, 'Maria' ,60, 'BR', 1), + (10, 'João' ,27, 'BR', 0), + (11, 'Kenji' ,33, 'JP', 1), + (12, 'Yuki' ,29, 'JP', 0), + (13, 'Chen' ,40, 'CN', 1), + (14, 'Li' ,48, 'CN', 1), + (15, 'Fatima',31, 'AE', 1), + (16, 'Ahmed' ,36, 'AE', 1), + (17, 'George',24, 'US', 1), + (18, 'Nina' ,34, 'RU', 1), + (19, 'Max' ,50, 'DE', 1), + (20, 'Sophie',22, 'DE', 0) + + --1. Получи 10 самых старших **активных** пользователей. + select + user_id, + name, + age, + country, + is_active + from mdrozdov.users + where is_active + order by age desc + limit 10 + --2. Выведи пользователей из `country = 'RU'`, отсортированных по имени. + select + user_id, + name, + age, + country, + is_active + from mdrozdov.users + where country = 'RU' + order by name asc + --3. Что произойдёт, если написать `LIMIT 10` **без `ORDER BY`**? + -- ответ будет недетерминирован + --4. Посчитай количество пользователей по странам. + select + country, + count(user_id) as total_users + from mdrozdov.users + group by country + --5. Для каждой страны выведи минимальный и максимальный возраст. + select + country, + min(age) as min_user_age, + max(age) as max_user_age + from mdrozdov.users + group by country + --6. Отсортируй страны по убыванию количества пользователей. + select + country, + count(user_id) as total_users + from mdrozdov.users + group by country + order by total_users desc + + -- task 2 + CREATE TABLE mdrozdov.events ( + user_id Int, + event_time DateTime, + event_type String --mb better enum but idc + ) + engine = MergeTree + order by user_id + insert into events values + (1, '2025-07-17 09:00:00', 'click'), + (1, '2025-07-17 09:05:00', 'view'), + (1, '2025-07-17 09:10:00', 'purchase'), + (2, '2025-07-17 10:00:00', 'click'), + (2, '2025-07-17 10:05:00', 'click'), + (2, '2025-07-17 11:00:00', 'view'), + (3, '2025-07-16 12:00:00', 'view'), + (3, '2025-07-16 12:30:00', 'click'), + (3, '2025-07-17 08:00:00', 'purchase'), + (4, '2025-07-17 13:00:00', 'click'), + (4, '2025-07-17 14:00:00', 'view'), + (4, '2025-07-17 15:00:00', 'view'), + (5, '2025-07-16 20:00:00', 'view'), + (5, '2025-07-17 01:00:00', 'click'), + (5, '2025-07-17 06:00:00', 'purchase'), + (6, '2025-07-17 07:30:00', 'view'), + (6, '2025-07-17 08:45:00', 'click'), + (7, '2025-07-16 23:59:59', 'click'), + (7, '2025-07-17 00:01:00', 'purchase'), + (7, '2025-07-17 10:10:00', 'view') + --1-2) Добавь колонку с кумулятивным количеством событий по user_id (используй count() OVER ...) + select + user_id, + event_time, + event_type, + count(*) over (partition by user_id order by event_time) as total_click_exact_moment, + row_number() over (partition by user_id order by event_time) as row_number + from mdrozdov.events + --3) да, поддерживается, но не обязательно для коммулятивного суммирования строк + --4-6) Сделай WITH recent_events AS (...) — все события за последние 24 часа. + with mdrozdov.recent_events AS ( + select * + from mdrozdov.events + where event_time >= toDateTime('2025-07-18') - interval 1 day + ), category_list as ( + select 'click' as event_type, 'interaction' as category + union all + select 'view', 'interaction' + union all + select 'purchase', 'conversion' + ) + select + user_id, + count(*) as total_events_last_24h, + count(distinct cl.category) as uniq_categories + from recent_events re + left join category_list cl + using(event_type) + group by user_id + order by total_events_last_24h desc, user_id asc + limit 5 \ No newline at end of file diff --git a/mdrozdov/clickhouse/secondary_index.sql b/mdrozdov/clickhouse/secondary_index.sql new file mode 100644 index 0000000..53f172c --- /dev/null +++ b/mdrozdov/clickhouse/secondary_index.sql @@ -0,0 +1,65 @@ +--task 1 +create table mdrozdov.logs3 +on CLUSTER clickhouse_cluster ( + log_id Int, + ts DateTime +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/logs3', + '{replica}' + ) +order by ts + +alter table mdrozdov.logs3 +add index idx_ts ts type minmax GRANULARITY 2; + +select * +from mdrozdov.logs3 +where ts between '2024-06-02' and '2024-06-03' + +SELECT + query, + read_rows, + result_rows, + read_bytes +FROM system.query_log +WHERE query LIKE '%logs%' +ORDER BY event_time DESC +LIMIT 1; + +-- task 2 +create table mdrozdov.clicks +on CLUSTER clickhouse_cluster ( + id UInt64, + country String +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/clicks', + '{replica}' + ) +order by id + +alter table mdrozdov.clicks +add index idx_country country type set(100) GRANULARITY 2 + +Explain + select * + from mdrozdov.clicks + where country = 'RU' + +-- task 3 +create table mdrozdov.users +on CLUSTER clickhouse_cluster ( + id UInt64, + email String +) +engine = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mdrozdov/users', + '{replica}' + ) +order by id + +ALTER TABLE mdrozdov.users +ADD INDEX idx_email email TYPE bloom_filter(0.01) GRANULARITY 2; + +SELECT * FROM mdrozdov.users WHERE email = 'alice@mail.com'; \ No newline at end of file diff --git a/mdrozdov/clickhouse/view_distribute.sql b/mdrozdov/clickhouse/view_distribute.sql new file mode 100644 index 0000000..41ed079 --- /dev/null +++ b/mdrozdov/clickhouse/view_distribute.sql @@ -0,0 +1,34 @@ +-- task 1 + CREATE TABLE dist_table + ENGINE = Distributed( + 'cluster_name', -- нет доступа к конфигу infra clickhouse + 'mdrozdov', + 'logs', + rand() + ); + --rand() это параметр по которому мы распределяем данные при insert на разные ноды, в данном + --случае нам это нужно для равномерности распределения, потому что таблица не требует особых связей или тп + + -- task 2 + --Создай VIEW top_active_users, который показывает топ-100 пользователей по числу событий + CREATE VIEW top_active_users AS + select user_id, count(*) as total_events + from events + group by user_id + order by total_events desc + limit 100 + -- не подходит для дашборда с миллионами строк, потому что вычисляется каждый раз при вызове + -- для дашбордов лучше подойдет materialized view, тк хранит данные запроса и обновляется автоматически + -- при вставке новых данных + + --task 3 + CREATE MATERIALIZED VIEW + to + ENGINE = SummingMergeTree + order by date + mv_clicks_per_day AS + select + toDate(event_time) as date, + count(*) as total_clicks + from raw_events + group by date \ No newline at end of file diff --git a/mdrozdov/greenplum/core_layer.sql b/mdrozdov/greenplum/core_layer.sql new file mode 100644 index 0000000..8ff3a53 --- /dev/null +++ b/mdrozdov/greenplum/core_layer.sql @@ -0,0 +1,94 @@ +-- Измерение пользователей + CREATE TABLE core.dim_user ( + user_id INT PRIMARY KEY, + name TEXT, + age INT, + email TEXT, + registration_date DATE + ) + distributed by (user_id); + + -- Измерение курсов + CREATE TABLE core.dim_course ( + course_id INT PRIMARY KEY, + title TEXT, + category TEXT + ) + distributed by (course_id); + + -- Измерение уроков + CREATE TABLE core.dim_lesson ( + lesson_id INT PRIMARY KEY, + title TEXT, + duration_min INT, + course_id INT REFERENCES core.dim_course(course_id) + ) + distributed by (lesson_id); + + -- Опционально: измерение времени + CREATE TABLE core.dim_date ( + date DATE PRIMARY KEY, + year INT, + month INT, + day INT, + weekday TEXT + ) + distributed replicated; + + -- Таблицы фактов: + CREATE TABLE core.fact_lesson_views ( + user_id INT REFERENCES core.dim_user(user_id), + lesson_id INT REFERENCES core.dim_lesson(lesson_id), + course_id INT REFERENCES core.dim_course(course_id), + viewed_at TIMESTAMP + ) + distributed by (course_id); + + CREATE TABLE core.enrollments ( + user_id INT REFERENCES core.dim_user(user_id), + course_id INT REFERENCES core.dim_course(course_id), + enrolled_at TIMESTAMP + ) + distributed by (course_id); + + --ETL переброс данных из raw в core + INSERT INTO core.dim_user (user_id, name, age, email, registration_date) + SELECT DISTINCT + id::INT, + name::TEXT, + age::INT, + email::TEXT, + registration_date::DATE + FROM raw.users; + + INSERT INTO core.dim_course (course_id, title, category) + SELECT DISTINCT + id::INT, + title::TEXT, + category::TEXT + FROM raw.courses; + + INSERT INTO core.dim_lesson (lesson_id, title, duration_min, course_id) + SELECT DISTINCT + id::INT, + title::TEXT, + duration_min::INT, + course_id::INT + FROM raw.lessons; + + -- Заполнение таблицы фактов + insert into core.fact_lesson_views (user_id, lesson_id, course_id, viewed_at) + select + lw.user_id, + lw.lesson_id, + l.course_id, + lw.viewed_at + from raw.lessons_views lw + join raw.lessons l on lw.lesson_id = l.id; + + insert into core.enrollments (user_id, course_id, enrolled_at) + select + user_id, + course_id, + enrolled_at + from raw.enrollments; \ No newline at end of file diff --git a/mdrozdov/greenplum/dm_layer.sql b/mdrozdov/greenplum/dm_layer.sql new file mode 100644 index 0000000..991db90 --- /dev/null +++ b/mdrozdov/greenplum/dm_layer.sql @@ -0,0 +1,71 @@ +--lesson_popularity_summary data mart + create materialized view dm.lesson_popularity_summary as + select + dl.lesson_id, + dl.title as lesson_title, + dl.course_id, + dc.title as course_title, + count(flv.lesson_id) as total_views, + count(distinct flv.user_id) as unique_users, + min(flv.viewed_at) as first_view, + max(flv.viewed_at) as last_view + from core.dim_lesson dl + left join core.dim_course dc + on dl.course_id = dc.course_id + left join core.fact_lesson_views flv + on dl.lesson_id = flv.lesson_id + group by dl.lesson_id, dl.title, dl.course_id, dc.title + + -- inactive_users_summary data mart + create materialized view dm.inactive_users_summary as + select + du.user_id, + du.name as user_name, + du.email, + du.age, + count(e.course_id) as enrollments_count, + du.registration_date + from core.dim_user du + left join core.enrollments e + on du.user_id = e.user_id + left join core.fact_lesson_views flv + on du.user_id = flv.user_id + where flv.user_id is null + group by du.user_id, du.name, du.email, du.age, du.registration_date + + -- course_completion_rate data mart + create materialized view dm.course_completion_rate as + with total_lessons_per_course as( + select + course_id, + count(*) as lessons_in_course + from core.dim_lesson + group by course_id + ), lessons_viewed as ( + select + user_id, + course_id, + count(*) as lessons_viewed + from fact_lesson_views flv + group by user_id, course_id + ) + select + du.user_id as user_id, + du.name as user_name, + dc.course_id as course_id, + dc.title as course_title, + tlpc.lessons_in_course, + coalesce(lv.lessons_viewed, 0) as lessons_viewed, + round( + coalesce( + coalesce(lv.lessons_viewed, 0)*1.0/nullif(tlpc.lessons_in_course, 0), 0 + ), 2) as completion_rate + from core.enrollments e + join core.dim_user du + on e.user_id = du.user_id + join total_lessons_per_course tlpc + on tlpc.course_id = e.course_id + join core.dim_course dc + on e.course_id = dc.course_id + left join lessons_viewed lv + on du.user_id = lv.user_id and dc.course_id = lv.course_id \ No newline at end of file diff --git a/mdrozdov/greenplum/raw_layer.sql b/mdrozdov/greenplum/raw_layer.sql new file mode 100644 index 0000000..d2ab8c9 --- /dev/null +++ b/mdrozdov/greenplum/raw_layer.sql @@ -0,0 +1,44 @@ +create schema raw; + create schema core; + create schema dm; + + create table raw.users( + id INT, + name varchar(50), + age INT, + email varchar(50), + registration_date date + ) + distributed by (id); + + create table raw.courses ( + id INT, + title text, + category varchar(40), + created_at date + ) + distributed by (id); + + create table raw.lessons ( + id INT, + course_id INT, + title TEXT, + duration_min INT + ) + distributed by (course_id); + + create table raw.enrollments ( + id INT, + user_id INT, + course_id INT, + enrolled_at date + ) + distributed by (user_id); + + create table raw.lessons_views( + id int, + user_id int, + lesson_id int, + viewed_at timestamp + ) + distributed by (user_id) \ No newline at end of file