SQL оптимизация для аналитика
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это знать
Начинающий аналитик: «Мой query работает 40 минут». Middle: «Я посмотрел EXPLAIN, добавил index, теперь 5 секунд». Senior: «Я перестроил модель в warehouse, теперь milliseconds».
Оптимизация SQL — навык, который делает вас в разы продуктивнее. На собесах middle+ спрашивают обязательно.
Топ приёмов
1. EXPLAIN ANALYZE
Всегда начинать с explain plan.
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123;Seq Scan→ нет indexesIndex Scan→ используется indexHash Join→ parallel processingSort→ ORDER BY / Window function
2. Индексы на WHERE/JOIN
CREATE INDEX idx_orders_user_id ON orders(user_id);Composite для multi-column filters:
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);Order важен: left-to-right match.
3. Убрать function на column
-- Плохо
WHERE DATE(created_at) = '2026-04-23'
-- Хорошо (использует index)
WHERE created_at >= '2026-04-23' AND created_at < '2026-04-24'4. SELECT только нужные columns
-- Плохо
SELECT * FROM huge_table;
-- Хорошо
SELECT user_id, email FROM huge_table;Меньше IO → быстрее.
5. LIMIT для exploration
SELECT * FROM big_table LIMIT 100;Для первого взгляда на данные.
6. Filter рано
-- Плохо
SELECT * FROM big_table
JOIN small_table USING (id);
-- Лучше (filter до join)
SELECT * FROM (
SELECT * FROM big_table WHERE condition
) b
JOIN small_table USING (id);Less data → faster JOINs.
7. EXISTS vs IN
-- IN
SELECT * FROM a WHERE id IN (SELECT id FROM b);
-- EXISTS (often faster)
SELECT * FROM a WHERE EXISTS (SELECT 1 FROM b WHERE b.id = a.id);Особенно на big subquery результатах.
8. Union all vs union
-- UNION удаляет дубликаты (медленнее)
SELECT * FROM a UNION SELECT * FROM b;
-- UNION ALL (быстрее, если уверены no duplicates)
SELECT * FROM a UNION ALL SELECT * FROM b;9. Материализованные views
Для часто используемых тяжёлых queries:
CREATE MATERIALIZED VIEW daily_summary AS
SELECT DATE, SUM(total) FROM orders GROUP BY 1;
REFRESH MATERIALIZED VIEW daily_summary; -- обновить10. Partitioning
Для огромных tables — partition по time:
CREATE TABLE orders_2026_04 PARTITION OF orders
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');Query по month → scan только нужный partition.
Когда что использовать
Small table (< 1M rows)
Обычно index достаточно.
Medium (1M - 100M)
Index + avoid function on column + filter early.
Large (> 100M)
Partitioning + materialized views + careful query design.
Huge (billions)
Dedicated analytics DB: ClickHouse, BigQuery, Snowflake.
Database-specific
Postgres
EXPLAIN (ANALYZE, BUFFERS)— detailedANALYZE table— update statisticsVACUUM— очистка dead tuples
ClickHouse
- MergeTree engines
- Partition by date
- Primary key ≠ PK в Postgres (sort key)
BigQuery
- Partitioned / clustered tables
- Avoid SELECT * (billing по scanned data)
- Materialized views
MySQL
SHOW INDEXOPTIMIZE TABLE
Common anti-patterns
Function on indexed column
WHERE LOWER(email) = 'a' — no index use. Normalize при вставке.
Implicit type cast
WHERE user_id = '123' (number vs string) — cast breaks index.
Correlated subquery
SELECT (SELECT AVG(x) FROM t WHERE ... = o.id) FROM orders o;Caries per row. Rewrite as JOIN.
SELECT DISTINCT *
Often symptom ошибки в JOIN multiplying rows.
CTE считается медленнее?
Postgres < 12
CTE материализуются → может замедлять.
Postgres 12+
CTE inlined → no penalty.
MSSQL, Oracle
Всегда inlined.
Window functions
Window часто быстрее self-join:
-- Slow self-join
SELECT a.*, (SELECT COUNT(*) FROM orders b WHERE b.user_id = a.user_id)
FROM orders a;
-- Fast window
SELECT *, COUNT(*) OVER (PARTITION BY user_id) FROM orders;На собесе
«Запрос 5 минут. Ускорь».
Алгоритм:
- EXPLAIN ANALYZE
- Identify bottleneck (Seq Scan, Sort, Hash Join)
- Add index, rewrite, materialize — whichever fits
- Test
Walk through 3-5 шагов — middle ответ. Show domain knowledge.
Связанные темы
FAQ
Best resource?
«Use the Index, Luke!» — free online book.
Index «всегда хорошо»?
Нет. Каждый index — overhead для INSERT/UPDATE.
Когда partitioning нужен?
100M rows на table, частые time-based queries.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.