сделал DDL для пет проекта
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
-- Задание 1. Оформление закзаза
|
||||
INSERT INTO mnoskov.orders (id, user_id, order_date, status) VALUES
|
||||
(9, 10, '2026-03-13', 'paid'),
|
||||
(10, 10, '2026-03-14', 'paid');
|
||||
|
||||
INSERT INTO mnoskov.order_items (order_id, product_id, quantity, price_at_purchase) VALUES
|
||||
(9, 3, 2, 88000),
|
||||
(10, 6, 1, 5900);
|
||||
|
||||
UPDATE mnoskov.products
|
||||
SET in_stock = FALSE
|
||||
WHERE id IN(3, 6);
|
||||
-- Если выполнятся не все шаги, то транзакция откатится и все останется как было
|
||||
-- Потому что заказ если оформляется, то полностью, частично он выполняться не должен
|
||||
|
||||
|
||||
-- Задание 2
|
||||
-- При таком сценарии все должно откатаиться к самому началу, ничего не сохранится
|
||||
-- Если не делать rollback, то можно получить заказы без продуктов, или без стомости, или полностью оформленный заказ, но продуктов нет в наличии
|
||||
-- Любые 'битые' заказы повлияют на отчеты и витрины
|
||||
|
||||
|
||||
-- Задание 3
|
||||
-- При изменении цены товара в products, это действие могут начать выполнять два человека одновременное, и если это не будет транзакцией то получится непонятно что, а если как транзакция, то сначала запишется 1 результат, а потом 2, и не будет никаких ошибок
|
||||
-- Если выполняетя как транзакция, то просто откатится назад и все будет супер
|
||||
-- Тк база сама откатывает транзакции в случае надобности, это освобождает инженера от доли ответственности
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Задание 1
|
||||
SELECT * FROM mnoskov.users WHERE id = 1;
|
||||
-- Если две одновременные сессии выполнят этот запрос, то в обеих все выведется, и ошибок или очереди не бкдет
|
||||
-- Потому что этот запрос только на чтение
|
||||
|
||||
|
||||
-- Задание 2
|
||||
-- Первая сессия будет просто ждать окончания выполнения первой
|
||||
-- Если разрешить обоимUPDATE пройти одновременно, то получится какая-то херня пологаю, они смешаются, или че-то такое
|
||||
-- У Alice в итоге останется второй email, тк он выполнится последним. Порядок COMMIT важен, тк в последствии этого порядка будут выполняться запросы
|
||||
|
||||
|
||||
-- Задание 3
|
||||
-- select выполнится тк просто читает, а update встанет в очередь, тк он попытается изменить 'забронированные данные'
|
||||
-- FOR UPDATE помогает избежать ошибок когда происходит покупка товара или че то такое, когда идет процесс изменений в данных
|
||||
|
||||
|
||||
-- Задание 4
|
||||
-- Дедлок возникает потому что каждая сессия заблокировал то, что нужно другой сессии. База отменит одну из сессий, чтобы прошла хоть какая-то
|
||||
-- Чтобы дедлок не возникал, нужно обновления все производить в одном порядке
|
||||
-- Если при массовых обновлениях не соблюдать порядок строк, то будет возникать жопа из дедлоков
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
-- Задание 1
|
||||
-- Вариант А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, age
|
||||
FROM mnoskov.users
|
||||
WHERE country = 'RU' AND age > 25;
|
||||
|
||||
-- Вариант Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, age
|
||||
FROM mnoskov.users
|
||||
WHERE age > 25 AND country = 'RU';
|
||||
-- Выводят они один и тот же результат, потому что условия у них одинаковые
|
||||
-- По скорости выполнения судя по планировщику тоже одинаковоы
|
||||
|
||||
|
||||
-- Задание 2
|
||||
-- Вариант 1
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
u.email,
|
||||
o.order_date::date,
|
||||
o.status
|
||||
FROM
|
||||
mnoskov.users u
|
||||
JOIN
|
||||
mnoskov.orders o ON u.id = o.user_id
|
||||
WHERE o.status = 'refunded';
|
||||
|
||||
-- Вариант 2
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
u.email,
|
||||
o.order_date::date,
|
||||
o.status
|
||||
FROM
|
||||
mnoskov.orders o
|
||||
JOIN
|
||||
mnoskov.users u ON o.user_id = u.id
|
||||
WHERE o.status = 'refunded';
|
||||
-- Между первой и второй таблицей результаты одинаковые. Если в запросе нет никаких условий связанных с таблицами, то первой оптимизатор смотреть users, но если если условие по orders.status, то сначала он сканирует orders
|
||||
|
||||
|
||||
-- Задание 3
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, email
|
||||
FROM mnoskov.users
|
||||
WHERE country = 'RU';
|
||||
-- Тип сканирования: Seq Scan
|
||||
-- Количество прочитанных строк: 1
|
||||
-- Время выполнения примерно: 0.023 ms(+-5)
|
||||
|
||||
|
||||
-- Задание 4
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT *
|
||||
FROM mnoskov.users
|
||||
WHERE country = 'RU';
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, email
|
||||
FROM mnoskov.users
|
||||
WHERE country = 'RU';
|
||||
-- Поскольку данных в таблице мало у меня время выполнения одинаковое, но могу предположить, что второй выполнился быстрее, тк у него в селекте два поля, а не вся таблица
|
||||
|
||||
|
||||
-- Задание 5
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name, o.id, o.order_date
|
||||
FROM
|
||||
mnoskov.users u
|
||||
JOIN
|
||||
mnoskov.orders o ON u.id = o.user_id
|
||||
WHERE
|
||||
u.country = 'RU';
|
||||
-- Читать начала с users, тк мы в where пишем условие для users, и быстрее сначала отсеять ненужных юзеров
|
||||
|
||||
|
||||
-- Задание 6
|
||||
SELECT u.name, COUNT(o.id) AS orders_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.country = 'Россия'
|
||||
AND LOWER(u.name) LIKE '%алекс%'
|
||||
GROUP BY u.name;
|
||||
-- Его замедляет LOWER(не надо использовать функции), и думаю что LIKE(лучше использовать просто =), и ещэ group by по имени - это дольше чем по первичному ключу(надо группировать по ПК)
|
||||
|
||||
|
||||
-- Задание 7
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, p.name AS product_name, oi.quantity
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
JOIN order_items oi ON o.id = oi.order_id
|
||||
JOIN products p ON oi.product_id = p.id
|
||||
WHERE u.country = 'RU';
|
||||
-- users -> orders -> order_items -> products - читает в таком порядке, потому что в where условие по users
|
||||
-- Если убрать where, то будет - order_items -> users -> orders -> products
|
||||
-- Как ускорить не знаю, может индексов добавть, но это пока не проходил
|
||||
|
||||
-- Задание 8
|
||||
-- Вариант А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, price
|
||||
FROM mnoskov.products
|
||||
WHERE category = 'Электроника'
|
||||
AND price > 50000;
|
||||
|
||||
-- Вариант Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT *
|
||||
FROM mnoskov.products
|
||||
WHERE category = 'Электроника'
|
||||
AND price > 50000;
|
||||
-- Данных в таблицах мало, врем выполнения не оценить, но смотря просто со стороны понятно что выриант А будет быстрее, потому что у него пару колонок в селекте, а не вся таблица как в Б
|
||||
|
||||
|
||||
-- Задание 9
|
||||
-- Вариант коллег
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE EXTRACT(YEAR FROM created_at) = 2026
|
||||
AND LOWER(country) = 'россия'
|
||||
AND age + 10 > 35;
|
||||
|
||||
-- Мой вариант
|
||||
SELECT
|
||||
id, name, age, is_active
|
||||
FROM
|
||||
mnoskov.users
|
||||
WHERE
|
||||
created_at BETWEEN '2026-01-01' AND '2027-01-02' AND
|
||||
country = 'RU' AND
|
||||
age > 45;
|
||||
-- Мой вариант лучше потому что:
|
||||
-- нет функций и мат. вычислений в where, все приведено к самому легкочитаемому формату
|
||||
-- нет звездочки в select, а только то, что нужно
|
||||
|
||||
|
||||
-- Задание 10
|
||||
INSERT INTO users (id, name, age, email, country, created_at)
|
||||
SELECT
|
||||
g AS id,
|
||||
'user_' || g AS name,
|
||||
27 AS age,
|
||||
'user_' || g || '@example.com' AS email,
|
||||
'RU' AS country,
|
||||
'2026-02-03' AS created_at
|
||||
FROM generate_series(20001, 50000) AS g; -- GPT сделал запрос мне
|
||||
|
||||
SELECT * FROM mnoskov.users WHERE age IS NOT NULL;
|
||||
|
||||
-- Запрос 1
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM users WHERE age > 25;
|
||||
|
||||
-- Запрос 2
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, email FROM users WHERE age > 25;
|
||||
-- Ну чето все равно не видна разница во времени выполнения, но по логике должен быть быстрее второй запрос
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
-- Задание 1
|
||||
EXPLAIN
|
||||
SELECT
|
||||
name, email
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
id = 1;
|
||||
-- Использует index scan
|
||||
-- Index Cond: (id = 1) - это значит что условие id = 1, и оно выполняется по индексу, и что id - индекс
|
||||
-- База использует индекс, потому что у нас по полю id создан индекс, и база выбирает наиболее быстрый пусть ввыполнения запроса - через индекс
|
||||
|
||||
|
||||
-- Задание 2
|
||||
-- Запрос А
|
||||
EXPLAIN
|
||||
SELECT * FROM users WHERE id = 2;
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN
|
||||
SELECT * FROM users WHERE age > 25;
|
||||
-- В первом запрос index scan, потому что у нас уловие по индексу - id, во втором запросы seq scan, потому что у поля age не индексов, и таблица сканируется медленее
|
||||
|
||||
|
||||
-- Задание 3
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
name, price
|
||||
FROM
|
||||
products
|
||||
WHERE
|
||||
category = 'Электроника';
|
||||
-- Execution Time: 0.020 ms
|
||||
-- Тип сканирования: Seq Scan
|
||||
-- Ожидалось вернуть 1, а вернул 3
|
||||
|
||||
|
||||
-- Задание 4
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM users WHERE country = 'RU';
|
||||
-- Без индекса - Execution Time: 6.769 ms
|
||||
CREATE INDEX idx_country ON users(country);
|
||||
-- С индексом - Execution Time: 2.076 ms
|
||||
-- Сначала идет Bitmap Heap Scan on users - как я понял на этом этапе 'создается' таблица где country = 'RU', а потом Bitmap INdex Scan on idx_country - тут уже мы читам информацию из таблицы где все country = 'RU'.
|
||||
-- Запрос стал быстрее примерно в три раза
|
||||
-- Индекс помог, потому что база искала не среди всех данных, а среди 'проиндексированных'
|
||||
|
||||
|
||||
-- Задание 5
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name, o.order_date, o.status
|
||||
FROM
|
||||
users u
|
||||
JOIN
|
||||
orders o ON u.id = o.user_id
|
||||
WHERE
|
||||
u.country = 'RU';
|
||||
-- База исполщовала Nested Loop
|
||||
-- Чтение началось с orders
|
||||
-- Execution Time: 0.075 ms
|
||||
|
||||
|
||||
-- Задание 6
|
||||
CREATE INDEX idx_email ON users(email);
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
lower(email) = 'user_28@example.com';
|
||||
-- База не использует индекс пока в where есть функция lower
|
||||
-- Потому что в условии функция, а с ними индексы не в ладах как я понял
|
||||
-- Чтобы индекс заработал, надо убрать функцию lower, и сравнивать email как есть
|
||||
-- Если убрать lower, то Execution Time намного быстрее, прям очень намного быстрее
|
||||
|
||||
|
||||
-- Задание 7
|
||||
CREATE INDEX idx_age ON users(age);
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM users WHERE age + 5 > 30;
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM users WHERE age > 25;
|
||||
-- Быстрее работает запрос Б, но че то он тоже не хочет индекс использовать, хотя я его создал
|
||||
-- Первый запрос не может использовать индекс, потому что там в условии математическое вычисление, а почему второй запрос не использует индек - я хз
|
||||
-- Чтобы исправить запрос надо убрать сложение, и просто написать age > 25, как в запросе Б
|
||||
|
||||
|
||||
-- Задание 8
|
||||
CREATE INDEX idx_created_at ON users(created_at);
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, COUNT(o.id) AS orders_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE EXTRACT(YEAR FROM u.created_at) = 2026
|
||||
GROUP BY u.name;
|
||||
-- Функция EXTRACT - это плохая идея, потому что это функция которой быть в where по возможности не должно
|
||||
-- WHERE i.crated_at BETWEEN '2026-01-01' AND '2027-01-01'
|
||||
-- Индекс не используется в текущеа виде
|
||||
|
||||
|
||||
-- Задание 9
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM products WHERE price > 50000;
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT name, price FROM products WHERE price > 50000;
|
||||
-- Если будет много данных в таблице, то быстрее будет работать второй запрос
|
||||
-- Второй эффективнее тк в селекте указаны определнные поля, а не все(*)
|
||||
-- Разница будет особо замета при больших объемах данных
|
||||
|
||||
|
||||
-- Задание 10
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, p.name AS product_name, oi.quantity
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
JOIN order_items oi ON o.id = oi.order_id
|
||||
JOIN products p ON oi.product_id = p.id
|
||||
WHERE u.country = 'RU' AND p.category = 'Электроника';
|
||||
-- Порядок чтения таблиц: products -> order_items -> orders -> users
|
||||
-- Количество строк: products - 3, order_items - 12, orders - 1, users - 0
|
||||
-- Если у country и category будут индексы, то запрос ьудет выполняться побыстре, потому что будет Index Scan, а не Seq Scan
|
||||
|
||||
|
||||
-- Задание 11
|
||||
-- До
|
||||
EXPLAIN ANALYZE
|
||||
SELECT *
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE LOWER(u.name) LIKE '%иван%'
|
||||
AND EXTRACT(MONTH FROM o.order_date) = 1
|
||||
AND u.age + 10 > 35;
|
||||
-- Execution Time: 0.219 ms
|
||||
|
||||
-- После
|
||||
EXPLAIN ANALYZE
|
||||
SELECT *
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.name = 'иван' AND
|
||||
o.order_date BETWEEN '2024-01-01' AND '2024-02-01' AND
|
||||
O.order_date BETWEEN '2026-01-01' AND '2026-02-01' AND
|
||||
u.age > 25;
|
||||
-- Execution Time: 0.032 ms
|
||||
-- Мой вариант лучще, потому что у меня нет функций и вычисление мешаюзих использовать индексы в условии, и оптимизатор может успешно использовать индексы
|
||||
|
||||
|
||||
-- Задание 12
|
||||
-- Запрос 1
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
o.user_id,
|
||||
o.id AS order_id
|
||||
FROM
|
||||
users u
|
||||
JOIN
|
||||
orders o ON u.id = o.user_id AND u.country = 'RU';
|
||||
|
||||
-- Запрос 2
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
o.user_id,
|
||||
o.id AS order_id
|
||||
FROM
|
||||
orders o
|
||||
WHERE EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
users u
|
||||
WHERE
|
||||
o.user_id = u.id AND
|
||||
u.country = 'RU'
|
||||
);
|
||||
|
||||
-- Запрос 3
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
o.user_id,
|
||||
o.id AS order_id
|
||||
FROM
|
||||
orders o
|
||||
WHERE o.user_id IN (
|
||||
SELECT
|
||||
u.id
|
||||
FROM
|
||||
users u
|
||||
WHERE
|
||||
u.country = 'RU'
|
||||
);
|
||||
|
||||
-- Запрос 1: Execution Time: 0.055 ms
|
||||
-- Запрос 2: Execution Time: 0.051 ms
|
||||
-- Запрос 3: Execution Time: 0.073 ms
|
||||
-- Они все дют примерно одинаковый разультат, потому что мало данных в таблице. А планы выполнения у них у всех одинаковые, вот прям 1 в 1. Ну по логие 3 запрос самый медленный, а первые два шустрые
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
-- Задание 1
|
||||
EXPLAIN ANALYZE
|
||||
SELECT country, count(*) AS users_count
|
||||
FROM users
|
||||
WHERE is_active = TRUE
|
||||
GROUP BY country
|
||||
HAVING count(*) > 1
|
||||
ORDER BY users_count DESC;
|
||||
-- WHERE применится до GROUP BY
|
||||
-- HAVING применится после GROUP BY
|
||||
-- Использовать users_count в HAVING нельзя, а в ORDER BY можно
|
||||
|
||||
|
||||
-- Задание 2
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date, o.status
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.country = 'RU' AND o.status = 'paid'
|
||||
ORDER BY o.order_date DESC;
|
||||
-- Порядок выполнения по QUERY PLAN:
|
||||
-- 1) сначала сканирутся таблица orders и выполняется фильтрация o.status = 'paid',
|
||||
-- 2) потом сканируется таблица users по условию u.id = o.user_id, и выполняется вильтрация u.country = 'RU',
|
||||
-- 3) далее Nested Loop соединяет orders и users, которые уже прошли фильтры,
|
||||
-- 4) теперь выводится выполняется сортирвка o.order_date DESC
|
||||
|
||||
-- Порядок выполнения по логике:
|
||||
-- 1) сначала сканируем таблицу users и присоединяем orders по условию u.id = o.user_id
|
||||
-- 2) потом фильтруем соединенные таблицы по u.country = 'RU' и o.status = 'paid'
|
||||
-- 3) в SELECT выбираем поля которые хотим вывести - u.name, o.order_date, o.status
|
||||
-- 4) сортируем выбранные поля по o.order_date по убыванию
|
||||
|
||||
|
||||
-- Задание 3
|
||||
-- Запрос А
|
||||
SELECT country, AVG(age) AS avg_age
|
||||
FROM users
|
||||
GROUP BY country
|
||||
HAVING country = 'Россия';
|
||||
-- Работает медленно потому having фильтрет после группировки а не до
|
||||
|
||||
-- Запрос Б
|
||||
SELECT country, avg(age) AS avg_age
|
||||
FROM users
|
||||
WHERE country = 'RU'
|
||||
GROUP BY country;
|
||||
-- Этот вариант быстрее, тк он фильтрует строки до группировки, те группируется меньше строк а не все
|
||||
|
||||
|
||||
-- Задание 4
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT category, AVG(price) AS avg_price
|
||||
FROM products
|
||||
GROUP BY category
|
||||
HAVING category = 'Электроника' AND AVG(price) > 20000;
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT category, AVG(price) AS avg_price
|
||||
FROM products
|
||||
WHERE category = 'Электроника'
|
||||
GROUP BY category
|
||||
HAVING AVG(price) > 20000;
|
||||
-- Количество обработанных строк у запроса А: 4
|
||||
-- Количество обработанных строк у запроса Б: 4
|
||||
-- PostgreSQL походу оптимизирует даже хуйню написанную в запросе А, планы запросов А и Б одинаковые - 1 в 1, хотя по сути у запроса А должно быть больше строк
|
||||
|
||||
|
||||
-- Задание 5
|
||||
-- Изначальный запрос
|
||||
SELECT
|
||||
country,
|
||||
COUNT(*) AS users_count
|
||||
FROM users
|
||||
WHERE users_count > 1
|
||||
GROUP BY country
|
||||
ORDER BY country;
|
||||
|
||||
-- Исправленный запрос
|
||||
SELECT
|
||||
country,
|
||||
COUNT(*) AS users_count
|
||||
FROM users
|
||||
GROUP BY country
|
||||
HAVING COUNT(*) > 1
|
||||
ORDER BY country;
|
||||
|
||||
-- Задание 6
|
||||
SELECT
|
||||
u.country,
|
||||
COUNT(o.id) AS orders_count,
|
||||
AVG(o.id) AS avg_order_id
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.is_active = true
|
||||
GROUP BY u.country
|
||||
HAVING COUNT(o.id) > 1
|
||||
ORDER BY orders_count DESC
|
||||
LIMIT 3;
|
||||
-- Полный порядок выполнения:
|
||||
-- 1) FROM/JOIN - сканируется таблица users, и к ней присоединяется таблица orders по условию u.id = o.user_id
|
||||
-- 2) WHERE - из двух соединенных таблиц фильтруются и остаются только строки где u.is_active = true
|
||||
-- 3) GROUP BY - происходится группировка по u.country, те все строки с одной и той же страной(записью в столбце) сжимаются в одну
|
||||
-- 4) HAVING - уже по сгруппированным строкам проводится фильтрация по COUNT(o.id) > 1, остются только те страны(строки) где кол-во пользователей больше одного
|
||||
-- 5) SELECT - выбираются столбцы которые будут выведены: u.country, COUNT(o.id) AS orders_count, AVG(o.id) AS avg_order_id
|
||||
-- 6) ORDER BY - выбранные в SELECT строки сортируются по orders_count по убыванию
|
||||
-- 7) LIMIT - из отсортированных строк остаются только первые 3
|
||||
|
||||
|
||||
-- Задание 7
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT category, COUNT(*)
|
||||
FROM products
|
||||
GROUP BY category
|
||||
HAVING category = 'Электроника';
|
||||
|
||||
-- Запрос Б
|
||||
EXPLAIN ANALYZE
|
||||
SELECT category, COUNT(*)
|
||||
FROM products
|
||||
WHERE category = 'Электроника'
|
||||
GROUP BY category;
|
||||
-- по логике запрос Б должен быть быстрее тк он сначала филтрует строки а потом группирует уже отфильтрованное,
|
||||
-- НО PostgreSql может оптимизровать и запрос А, и получается что выполняются оба запроса одинаково, сначала фильтрация а потом группировка
|
||||
-- В обоих запросах на шаге 'Seq Scan on products' обрабатываются: 3 строки, а на 'GroupAggregate': 1 строка
|
||||
-- Execution Time одно и тоже
|
||||
|
||||
|
||||
-- Задание 8
|
||||
SELECT
|
||||
u.name,
|
||||
COUNT(o.id) AS orders_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.country = 'Россия'
|
||||
AND u.age > 25
|
||||
AND o.status = 'completed'
|
||||
GROUP BY u.name
|
||||
HAVING COUNT(o.id) >= 2
|
||||
ORDER BY orders_count DESC;
|
||||
-- WHERE применяется между FROM/JOIN и GROUP BY
|
||||
-- HAVING применяется между GROUP BY и SELECT
|
||||
-- фильтрация по o.status в WHERE происходит группировки строк, а фильтрация по COUNT(o.id) в HAVING происходит уже после группирациюю, по самим сгруппированным строкам
|
||||
|
||||
|
||||
-- Задание 9
|
||||
SELECT
|
||||
u.name,
|
||||
p.name AS product_name,
|
||||
p.price
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
JOIN order_items oi ON o.id = oi.order_id
|
||||
JOIN products p ON oi.product_id = p.id
|
||||
WHERE u.country = 'Россия'
|
||||
ORDER BY p.price DESC
|
||||
LIMIT 5;
|
||||
-- ORDER BY применяется после SELECT, перед LIMIT/OFFSET(если есть)
|
||||
-- LIMIT идет после GROUP BY, тк сначала нужно отфильтровать строки чтобы взять их кусок(5 строк)
|
||||
-- если LIMIT(5) выполнить до сортировки, то это будет бесполезно, возьмутся просто первые 5 строк, и уже они будут сортироваться, и по итогу выйдет хуйня
|
||||
|
||||
|
||||
-- Задание 10
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
category,
|
||||
count(id) AS count_product_in_category,
|
||||
avg(price) AS avg_category_price
|
||||
FROM
|
||||
products
|
||||
GROUP BY
|
||||
category
|
||||
HAVING
|
||||
count(id) >= 2 OR avg(price) > 15000
|
||||
ORDER BY
|
||||
avg_category_price DESC
|
||||
LIMIT 3;
|
||||
-- 1) FROM - сканируем poducts
|
||||
-- 2) GROUP BY - группируем по category
|
||||
-- 3) HAVING - фильтруем по агрегированной функции count(id)
|
||||
-- 4) SELECT - делаем выборку по столбцам category, count_product_in_category, avg_category_price
|
||||
-- 5) ORDER BY - сортируем по avg_category_price по убыванию
|
||||
-- 6) LIMIT - выбираем первые 3 строки из отсортированных
|
||||
-- Ипсользовал HAVING тк по условию все фильтрации по агрегированным функциям
|
||||
-- Выполнил с EXPLAIN ANALYZE, сначала читаетя porducts, потом делается фильтрации по count(id) >= 2 и avg(price) > 15000, далее sort, а потом Limit
|
||||
|
||||
|
||||
-- Задание 11
|
||||
-- Запрос А
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
u.age,
|
||||
(SELECT COUNT(*)
|
||||
FROM orders o
|
||||
WHERE o.user_id = u.id) AS orders_count
|
||||
FROM users u
|
||||
WHERE u.country = 'RU'
|
||||
ORDER BY orders_count DESC;
|
||||
-- Подзапрос в SELECT выполняется самым первым, до начала остального запроса, и называется в планировщие - SubPlan 1
|
||||
-- orders_count нельзя использовать в WHERE
|
||||
-- orders_count можно использовать в ORDER BY
|
||||
|
||||
-- Запрос Б(исправленный)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
u.age,
|
||||
count(o.id) AS orders_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.country = 'RU'
|
||||
GROUP BY u.name, u.age
|
||||
ORDER BY orders_count DESC;
|
||||
-- Запрос Б(исправленный) - быстрее, тк подзапрос в SELECT делается для каждого пользователя заново, а JOIN всего один раз
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
-- Задание 1
|
||||
-- Ситуация -- Какой JOIN выберет база? -- Почему?
|
||||
--------------------------------------------------------------------------------------
|
||||
-- 10 пользователей, 1 млн заказов,| Nested | Маленькая таблица и большая с индексом. |
|
||||
-- есть индекс на orders.user_id | Loop | База будет искать совпадения из |
|
||||
-- | | маленькой по индексу из большой. |
|
||||
--------------------------------------------------------------------------------------
|
||||
-- 100 000 пользователей, | Hash | Таблицы среднего размера, нет индексов. |
|
||||
-- 100 000 заказов, нет индексов | Join | База построит хэш-таблицу одной из них, |
|
||||
-- | | и будет искать совпадения в другой. |
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Обе таблицы отсортированы по | Merge | Потому что таблицы отсортированы по |
|
||||
-- ключу, есть индексы | Join | ключу. Будет происходить соединение - |
|
||||
-- | | 'Одновременное&последовательное'. |
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Маленькая таблица (5 строк) | Nested | База будет искать совпадения из |
|
||||
-- соединяется с большой (1 млн), | Loop | маленькой в большой. Но тк нет индексов |
|
||||
-- нет индекса | | - это будет медленно. |
|
||||
-- |--------| Я засомневался и разобрал с ГПТ: |
|
||||
-- | Hash | Nested Loop будет дорого(без индексов - |
|
||||
-- | Join | 5.000.000 проврок), поэтому скорее всего|
|
||||
-- | | Hash Join, просто построится хэш-таблица|
|
||||
-- | | по маленькой и 5 раз отсканирует большую|
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Я заебался строить эту таблицу из пробелов. Хуй когда решусь на это снова. |
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- Задание 2
|
||||
-- Маленькая таблица (10 строк)
|
||||
CREATE TABLE mnoskov.small_users AS
|
||||
SELECT generate_series(1, 10) AS id, 'User ' || generate_series(1, 10) AS name;
|
||||
|
||||
-- Большая таблица (100 000 строк)
|
||||
CREATE TABLE mnoskov.big_orders AS
|
||||
SELECT
|
||||
generate_series(1, 100000) AS id,
|
||||
(random() * 9 + 1)::int AS user_id,
|
||||
CURRENT_DATE - (random() * 365)::int AS order_date;
|
||||
|
||||
-- Вариант 1: Без индекса
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, COUNT(o.id)
|
||||
FROM small_users u
|
||||
JOIN big_orders o ON u.id = o.user_id
|
||||
GROUP BY u.name;
|
||||
|
||||
-- Вариант 2: С индексом
|
||||
CREATE INDEX idx_big_orders_user_id ON big_orders(user_id);
|
||||
--DROP INDEX idx_big_orders_user_id;
|
||||
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, COUNT(o.id)
|
||||
FROM small_users u
|
||||
JOIN big_orders o ON u.id = o.user_id
|
||||
GROUP BY u.name;
|
||||
-- Вариант 1: Merge Join - Потому что обе таблицы отсортированы с самого создания
|
||||
-- Варинат 2: Тоже Merge Join
|
||||
-- База могла бы выбрать Nested Loop, потому что у нас одна таблица маленькая а другая большая и с индексом, но выбрала Merge Join, потому что таблицы отсортированы
|
||||
-- С индексом Execution Time: 31.579 ms, а без примерно 80.000 ms
|
||||
|
||||
|
||||
-- Задание 3
|
||||
-- Вариант 1: Без ORDER BY
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id;
|
||||
|
||||
-- Вариант 2: С ORDER BY по ключу соединения
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
ORDER BY u.id, o.user_id;
|
||||
|
||||
-- Вариант 3: С ORDER BY по другому полю
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
ORDER BY o.order_date;
|
||||
-- Во втором варианте также как и в первом - Nested Loop
|
||||
-- Merge Join не появился
|
||||
-- ORDER BY может повлиять на выбор JOIN, тк если есть стортировка, то база может выбрать Merge Join
|
||||
|
||||
|
||||
-- Задание 4
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
p.name AS product_name,
|
||||
oi.quantity,
|
||||
o.order_date
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id
|
||||
JOIN order_items oi ON o.id = oi.order_id
|
||||
JOIN products p ON oi.product_id = p.id
|
||||
WHERE u.country = 'RU';
|
||||
-- (u.id = o.user_id) - Nested Loop; (o.id = oi.order_id) - Nested Loop; (oi.product_id = p.id) - Hash Join
|
||||
-- Seq Scan на этапах сканирования orders и products
|
||||
-- Думаю стоит создать индекс на orders.user_id; и надо было бы на users.countyr, но я создавал уже в предидущих укроках; подумал, что надо ещё products.id, но там id и так первичный ключ, значит и индекс есть
|
||||
CREATE INDEX idx_orders_user_id ON orders(user_id); -- DROP INDEX idx_orders_user_id;
|
||||
-- После создания индекса типы JOIN не поменялись, план остался тем же, скорость выполнения запроса та же
|
||||
|
||||
|
||||
-- Задание 5
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
u.name,
|
||||
COUNT(DISTINCT o.id) AS orders_count,
|
||||
COUNT(DISTINCT oi.product_id) AS products_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
LEFT JOIN order_items oi ON o.id = oi.order_id
|
||||
WHERE u.country = 'RU'
|
||||
GROUP BY u.name;
|
||||
-- на этапе LEFT JOIN order_items oi ON o.id = oi.order_id - оптимизатор выполнил Hash Right Join по условию oi.order_id = o.id, то есть Hash Join
|
||||
-- на этапе LEFT JOIN orders o ON u.id = o.user_id - оптимизатор выполнил Hash Left Join по условию u.id = o.user_id, то есть тоже Hash Join
|
||||
-- Метод по сути один и тот же - Hash Join, но отличаются Right и Left - я думаю это выбрано для лучшей оптимизации
|
||||
-- Через Hash Right Join проходит - 12 строк
|
||||
-- Через Hash Left Join проходит - 11281 строк
|
||||
|
||||
|
||||
-- Задание 6
|
||||
-- Вариант A: INNER JOIN
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
INNER JOIN orders o ON u.id = o.user_id;
|
||||
|
||||
-- Вариант B: LEFT JOIN
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id;
|
||||
-- Разный тип используется, в варианте А - Nested Loop, в варианте B - Hash Left Join
|
||||
-- Да, у варанта B Execution Time в очень много раз больше
|
||||
-- LEFT JOIN медленне, потому что он не использует индекс, а делает Seq Scan, те читает таблицы полностью, потому что подругому не получится выполнить LEFT JOIN
|
||||
|
||||
|
||||
-- Задание 7
|
||||
-- Вариант 1: users JOIN orders
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
JOIN orders o ON u.id = o.user_id;
|
||||
|
||||
-- Вариант 2: orders JOIN users
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM orders o
|
||||
JOIN users u ON o.user_id = u.id;
|
||||
-- Тип JOIN не изменился
|
||||
-- Порядок чтения таблиц не изменился
|
||||
-- Execution Time одинаковое
|
||||
-- Оптимизатор выбрал один и тот же порядок потому что по сути это два одинаковых запроса, с одной и той же логикой, но написаны чуть чуть по разному, и оптимизатор в обоих случаях выполняет так, как быстрее. Почему порядок может быть разным, я не предполагаю.
|
||||
|
||||
|
||||
-- Задание 8
|
||||
-- Запрос 1
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
orders o
|
||||
JOIN
|
||||
order_items oi ON o.id = oi.order_id
|
||||
ORDER BY
|
||||
o.id, oi.order_id;
|
||||
-- Уверен такой запрос привел бы к Merge Join, если бы данных в таблицах было побольше
|
||||
|
||||
-- Запрос 2(из задания 2)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, COUNT(o.id)
|
||||
FROM small_users u
|
||||
JOIN big_orders o ON u.id = o.user_id
|
||||
GROUP BY u.name;
|
||||
-- Потому что обе таблицы уже отсортированы в момент создания
|
||||
|
||||
-- Задание 9
|
||||
-- Nested Loop лучше Hash Join, когда есть индексы по полям фильтрации, когда одна таблица небольшая, а у второй есть индекс. Подглядел: когда нужно найти немного совпадений.
|
||||
-- Hash Join лучше чем Nested Loop, когда нет индексов, когда таблицы среднего размера и больше, и когдау условие фильтрации '='
|
||||
|
||||
-- Оба запроса из задания 6:
|
||||
-- Вариант A: INNER JOIN
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
INNER JOIN orders o ON u.id = o.user_id;
|
||||
-- тут nested loop быстрее, тк соеденияется все по индексам
|
||||
|
||||
-- Вариант B: LEFT JOIN
|
||||
EXPLAIN ANALYZE
|
||||
SELECT u.name, o.order_date
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id;
|
||||
-- а тут быстрее merge join, тк по индексам соединить не получится, тут нужны обе полные таблицы, и создается по одной из них хэш-таблица
|
||||
|
||||
|
||||
-- Задание 10
|
||||
-- Запрос A
|
||||
EXPLAIN ANALYZE
|
||||
--CREATE MATERIALIZED VIEW mv_category_revenue AS
|
||||
SELECT
|
||||
p.category,
|
||||
sum(oi.quantity * oi.price_at_purchase) AS total_revenue
|
||||
FROM
|
||||
mnoskov.products p
|
||||
JOIN
|
||||
mnoskov.order_items oi ON p.id = oi.product_id
|
||||
JOIN
|
||||
mnoskov.orders o ON oi.order_id = o.id
|
||||
WHERE o.status = 'paid'
|
||||
GROUP BY
|
||||
p.category
|
||||
ORDER BY
|
||||
total_revenue desc;
|
||||
-- До: Execution Time: 0.309 ms
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
-- После добавления индекса ничего не поменялось, план запроса остался тот же, скорее всего потому что мало данных, думаю будь их больше с индексом запрос был бы быстрее
|
||||
|
||||
-- Запрос B
|
||||
EXPLAIN ANALYZE
|
||||
--CREATE MATERIALIZED VIEW mv_user_activity AS
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.name AS user_name,
|
||||
count(*) AS orders_count
|
||||
FROM
|
||||
mnoskov.users u
|
||||
JOIN
|
||||
mnoskov.orders o ON u.id = o.user_id
|
||||
WHERE
|
||||
o.status = 'paid'
|
||||
GROUP BY
|
||||
u.id, u.name
|
||||
HAVING
|
||||
count(*) > 0
|
||||
ORDER BY
|
||||
orders_count desc;
|
||||
-- И тут тоже несмотря на то что индекс на orders статус я уже создал, таблица orders скаринруется как Seq Scan, я думаю это потому, что мало данных, будь их больше с индексом запрос был бы быстрее
|
||||
|
||||
-- Запрос C
|
||||
EXPLAIN ANALYZE
|
||||
--CREATE MATERIALIZED VIEW mv_daily_revenue AS
|
||||
SELECT
|
||||
o.order_date::date AS sales_date,
|
||||
sum(oi.quantity * oi.price_at_purchase) AS daily_revenue
|
||||
FROM
|
||||
mnoskov.orders o
|
||||
JOIN
|
||||
mnoskov.order_items oi ON o.id = oi.order_id
|
||||
WHERE
|
||||
o.status = 'paid'
|
||||
GROUP BY
|
||||
o.order_date
|
||||
ORDER BY
|
||||
sales_date;
|
||||
-- Тут ситуация аналогичная и для первый двух запросов, нужные индексы уже созданы, в связи выполнения всех предидущих занятий и тем, и единственное что я мог сделать для улучшения времени выполнения - это добавить индекс на orders(status)
|
||||
Reference in New Issue
Block a user