added explain and poryadok sql

This commit is contained in:
Dmitry Medvedev
2026-06-30 13:54:42 +05:00
parent 03149fb22c
commit 3581c6c741
2 changed files with 398 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
--Задача 1 — Читаем план
Какой тип сканирования использует база?
--SeqScan - сканирование всей таблицы
Что означает строка `Index Cond: (id = 1)`?
-- У меня нет такой строки, есть "Filter: (id = 1)". Видимо это фильтрация по определенному значению, но в моем случае это должен быть индекс по столбцу id, но у меня индексы не проставлены, хорошо, что я хотя бы сейчас это уже понимаю.
Почему база использует индекс, а не читает всю таблицу?
--Потому что так быстрее и менее затратно.
----------
--Задача 2 — Seq Scan vs Index Scan
Почему в первом запросе используется Index Scan, а во втором может быть Seq Scan (если нет индекса на age)?
--Потому что Seq Scan сканирует таблицу полностью и ищет значения, а вот Index Scan нет необходимости сканировать всю таблицу, он отсекает по индексу.
----------
--Задача 3 — Замеряем время
1. Запиши **Execution Time**
--0.023 мс
2. Определи тип сканирования (Seq Scan или Index Scan)
--Seq Scan
3. Сколько строк вернул запрос?
--вернул 3 строки
----------
--Задача 4 — До и после индекса
Запиши Execution Time и тип сканирования.
--Seq Scan, 0.059 мс, после создания индекса запрос составил 0.026мс
Изменился ли тип сканирования?
-- Нет, не изменился, не могу понять почему.
Стал ли запрос быстрее? На сколько?
--Да, в 2 раза
Почему индекс помог?
--Потому что не пришлось искать по всей таблице.
----------
--Задача 5 — Анализируем JOIN
Какой тип JOIN использует база (Hash Join, Nested Loop, Merge Join)?
--Hash
С какой таблицы база начала чтение?
-- с users
Сколько времени занял весь запрос?
--execution time 0.032ms
----------
--Задача 6 — Почему медленно?
1. Выполни запрос и посмотри на план
--посмотрел
2. Использует ли база индекс?
--нет, используется seq scan
3. Почему НЕТ? (даже если индекс на email есть)
--Думаю, потому что также используется функция LOWER внутри WHERE
4. Как исправить запрос, чтобы индекс заработал?
CREATE INDEX idx_email ON users(email);
--я добавил индекс, но все равно EXPLAIN ANALYZE показывается Seq Scan. В чем проблема?
----------
--Задача 7 — Функции убивают индексы
Какой запрос работает быстрее?
--тот где нет дополнительной математики в условии
Почему первый запрос НЕ может использовать индекс на `age`?
--потому что появилась функция
Как правильно переписать запрос А?
-- убрать функцию из условия:
EXPLAIN ANALYZE
SELECT * FROM users WHERE age > 35;
----------
--Задача 8 — Диагностика медленного запроса
1. Почему функция `EXTRACT()` — плохая идея?
--потому что она мешает оптимизатору применить индекс.
2. Как переписать условие WHERE без функции?
--добавлением граничных условий что-то по типу created_at>='2026-01-01' AND created_at<'2027-01-01'
3. Будет ли использоваться индекс на `created_at` в текущем виде?
--нет, по описанным выше условиям
----------
--Задача 9 — SELECT * против конкретных колонок
Какой запрос работает быстрее?
-- запрос Б, там где выбираются не все столбцы
Почему второй может быть эффективнее (даже если тип сканирования одинаковый)?
--потому что объем обработанной информации меньше
В каких ситуациях разница будет особенно заметна?
--когда вся таблица изначально состоит из множества столбцов (более 10), а вт втором запросе мы берем только пару И когда количество строк очень большой.
----------
--Задача 10 — Оптимизируем JOIN с условием
1. Выполни запрос и посмотри на план
посмотрел
2. В каком порядке база читает таблицы?
БД читает таблицы в порядке, который является наименее затратным. Сначала идет отфильтровывание по условию,далее создается хэш таблица и хэш соединение, соединение по индексу.
3. Сколько строк проходит через каждый JOIN?
У меня здесь только 1 hash join - там 5 строк. Но сразу скажу, что одно из условий я удалил, так что у меня осталось только одно условие с category.
4. Можно ли оптимизировать? (Подсказка: подумай про индексы на `country` и `category`)
Я добавил индекс на category, поменялось количество hash join, стало 2.
Но сканирование не изменилось на индекс. Видимо, это связано с тем, что оптимизатору проще проходить по всей таблице, чем искать по индексу ввиду того, что таблица сама по себе не большая.
----------
--Задача 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;
Оптимизированный
EXPLAIN ANALYZE
SELECT u.name, o.order_date, u.age
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.name LIKE '%иван%'
AND o.order_date >= '2024-01-01'
AND o.order_date < '2024-02-01'
AND u.age > 25;
По моим данным ожидаемое кол-во строк = 0, поэтому и запросы отрабатываются быстро = 0.03 мс.
+247
View File
@@ -0,0 +1,247 @@
- **Задача 1** — Запрос с группировкой
SELECT country, COUNT(*) AS users_count
FROM users
WHERE is_active = true
GROUP BY country
HAVING COUNT(*) > 1
ORDER BY users_count DESC;
**❓ Вопросы:**
1. Когда применится `WHERE` — до или после `GROUP BY`?
До группировки, потому что это фильтрация строк
2. Когда применится `HAVING` — до или после `GROUP BY`?
После группировки, потому что это фильтрация групп
3. Можно ли использовать алиас `users_count` в `HAVING`?
Нельзя, тк HAVING применяется до SELECT
4. Можно ли использовать алиас `users_count` в `ORDER BY`?
Можно, тк ORDER BY применяется после SELECT
- **Задача 2** — JOIN с фильтрацией
SELECT u.name, o.order_date, o.status
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.country = 'Россия' AND o.status = 'completed'
ORDER BY o.order_date DESC;
**✅ Задание:** 
- Опиши порядок выполнения своими словами (как будто объясняешь другу-новичку).
На самом первом этапе идет формирование таблицы данных из фром и джойна, далее данные отфильтровываются в WHERE и выполняется вычисление выражение черезе SELECT, в конечном счете строки фильруются по order_date через ORDER BY
- **Задача 3** — Найди ошибку
Дан запрос:
SELECT country, AVG(age) AS avg_age
FROM users
GROUP BY country
HAVING country = 'Россия';
**❓ Вопросы:**
1. Запрос работает, но написан **неэффективно**. Почему?
Потому что фильтрация по "Россия" идет в самом конце запроса, когда можно было на первоначальном этапе сделать через  WHERE, чтобы не читать всю таблицу. Плюс можно добавить индекс по столюцу country
2. Перепиши его правильно, используя WHERE.
SELECT
country,
AVG(age) AS avg_age
FROM users
WHERE country = 'RU'
GROUP BY country;
3. Объясни, почему твой вариант быстрее.
Объяснил в 1ом вопросе.
- **Задача 4** — Исправь запрос
SELECT category, AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING category = 'Электроника' AND AVG(price) > 20000;
**✅ Задание:**
1. Перепиши запрос так, чтобы часть условий была в `WHERE`.
SELECT category, AVG(price) AS avg_price
FROM products
WHERE category = 'Электроника'
GROUP BY category
HAVING AVG(price) > 20000;
2. Объясни, почему это быстрее.
Потому что сразу происходит фильтрация строк и дальнейшая группировка и фильтрация уже не затрагивает ненужные строки (чем раньше фильтрация, тем лучше)
3. Нарисуй схему: сколько строк будет обработано в каждом случае.
Если не исполбзовать WHERE , то ан этапе группировке на входе будет 7 строк , которые далее отфильтуются.
А если сразу использовать WHERE, то на входе в группировку будет всего три строки
- **Задача 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
--WHERE users_count > 1
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;
**✅ Задание:** 
- Опиши ПОЛНЫЙ порядок выполнения (все 7 шагов) и объясни, что происходит на каждом.
1. Формирование таблицы данных из FROM и JOIN
2. Фильтрация строк WHERE
3. Группировка строк GROUP BY
4. Фильтрация групп HAVING
5. Вывод столбцов SELECT
6. Сортировка ORDER BY
7. Лимитирование выхода строк LIMIT
- **Задача 7** — Оптимизация
Сравни два запроса:
-- Запрос А
SELECT category, COUNT(*)
FROM products
GROUP BY category
HAVING category = 'Электроника';
-- Запрос Б
SELECT category, COUNT(*)
FROM products
WHERE category = 'Электроника'
GROUP BY category;
**❓ Вопросы:**
1. Какой запрос быстрее? Почему?
Запрос 2 быстрее потому что на этапе WHERE сразу отфильтровываются ненужные строки.
2. Нарисуй таблицу: на каком шаге сколько строк обрабатывается в каждом запросе.
На этапе FROM всего 7 строк. Далее если смотреть по запросу А они превращаются в 4 строки и на HAVING в 1.
Если смотреть на запрос Б, то после FROM остается 3 строки, содержащие данные только о категории 'Электроника', далее они просто группируются в одну.
3. Выполни оба с `EXPLAIN ANALYZE` и сравни `Execution Time.`
Запрос А - 0.04 мс
Запрос Б - 0.03 мс
- **Задача 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;
**❓ Вопросы:**
1. Когда применяются условия `WHERE`?
Для фильтрации строк
2. Когда применяется условие `HAVING`?
Для фильтрации групп
3. В чём разница между фильтрацией по `o.status` в `WHERE` и по `COUNT(o.id)` в `HAVING`?
В  WHERE фильтрация подразумевает выполненные заказы, а в HAVING подразумевает более 2х штук. Соответственно получается, что сначала мы фильтруем выполненные заказы, а потом людей у которых заказов более 2х штук.
- **Задача 9** — JOIN и ORDER BY
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;
**❓ Вопросы:**
1. На каком этапе применяется `ORDER BY`?
После FROM, JOIN, WHERE, SELECT и до LIMIT.
2. Почему `LIMIT` идёт после `ORDER BY`, а не до?
В таком случае нарушалась бы логика использования этих двух функций. Например, нужно вывести топ-3 (или топ-3 наименьшие) позиции из списка. И если лимит стоял бы до сортировки, то мы могли бы вывести совсем не топ значения. Данная последовательность помогает сначала сделать сортировка вверх или вниз и потом вывести нужные строки.
3. Что будет, если поменять их местами (технически невозможно, но подумай концептуально)?
В таком случае нарушалась бы логика использования этих двух функций. Например, нужно вывести топ-3 (или топ-3 наименьшие) позиции из списка. И если лимит стоял бы до сортировки, то мы могли бы вывести совсем не топ значения. Данная последовательность помогает сначала сделать сортировка вверх или вниз и потом вывести нужные строки.
- **Задача 10** — Напиши запрос с нуля
✅ Тебе нужно получить:
- Список категорий товаров
- Количество товаров в каждой категории
- Среднюю цену товаров в категории
- Только те категории, где средняя цена > 15000
- Только те категории, где товаров >= 2
- Отсортировать по средней цене (от большей к меньшей)
- Взять только топ-3
**✅ Задание:**
1. Напиши запрос
SELECT
p.category AS category,
COUNT(*) AS count_product_in_category,
AVG(price) AS avg_price_product_in_category
FROM products p
GROUP BY p.category
HAVING AVG(price) > 15000 AND COUNT(*) >= 2
ORDER BY avg_price_product_in_category DESC
LIMIT 3;
2. Опиши порядок выполнения
1. Формируется таблица из FROM
2. Группировка GROUP BY (Так как строки фильтровать не нужно)
3. Фильтрация групп HAVING
4. Вывод значений SELECT
5. Сортировка ORDER BY
6. Лимитирование LIMIT
3. Объясни, почему использовал `WHERE` или `HAVING` для каждого условия
Использовал только HAVING потому что по условию нужно фильтровать именно группы, а не строки.
4. Выполни с `EXPLAIN ANALYZE` и посмотри план
1. чтение таблицы products
2. Группировка с фильтрацией групп (hash aggregate)
3. Сортировка Sort key
4. LIMIT