Базы данных
SQL: SELECT, JOIN, GROUP BY
Chamberlin и Boyce придумали SEQUEL в 1974 году с одной целью: бухгалтер без знания математики должен уметь спросить базу данных 'покажи продажи за прошлый квартал'. Спустя 50 лет этот же язык обрабатывает 40 млн запросов в секунду в TikTok и хранит обучающие данные для GPT.
- ML pipeline: SELECT + JOIN для сборки датасетов из нескольких таблиц с метаданными
- Feature engineering: GROUP BY + агрегации как эффективная замена pandas groupby на больших объёмах
- A/B тестирование: GROUP BY variant + AVG(metric) для статистики экспериментов прямо в БД
- Experiment tracking: подзапросы для поиска лучшей модели каждого типа из MLflow backend
- Recommendation systems: многотабличные JOIN между users, items, interaction events
SELECT и WHERE: анатомия запроса
Instagram хранит миллиарды постов. Каждый раз, когда открывается лента, база данных за 50 мс отфильтровывает из этого океана ровно те записи, которые нужны одному конкретному пользователю. Инструмент - SELECT с WHERE. Chamberlin и Boyce создали SEQUEL в 1974 году специально чтобы обычные люди могли писать такие запросы без знания реляционной алгебры.
**Порядок выполнения SQL** отличается от порядка написания: FROM -> WHERE -> SELECT -> ORDER BY -> LIMIT. Это важно: WHERE не видит алиасы из SELECT.
**BETWEEN включает границы:** `WHERE age BETWEEN 18 AND 65` эквивалентно `age >= 18 AND age <= 65`. Для дат: `BETWEEN '2024-01-01' AND '2024-12-31'` включает весь последний день.
Запрос `SELECT alias_col FROM t WHERE alias_col > 5` (alias_col - псевдоним из SELECT) выдаст:
JOIN: связывание таблиц
Распространённый миф: JOIN медленный. Медленный JOIN - это признак отсутствия индекса на колонке соединения. PostgreSQL выполняет hash join на двух несортированных таблицах быстрее, чем Python-разработчик успевает написать `df.merge()`. Реляционная модель с 1970 года строится вокруг идеи: данные разбиты по смыслу, JOIN восстанавливает связи.
**Когда какой JOIN:** INNER - нужны только совпадающие данные (самый частый). LEFT - нужно сохранить все записи основной таблицы (аудит, отчёты). FULL OUTER - поиск расхождений между двумя источниками данных.
LEFT JOIN users и orders вернул строку с user.name='Bob' и order.amount=NULL. Это означает:
GROUP BY и HAVING: агрегация
GROUP BY не группирует данные в интуитивном смысле - он разбивает таблицу на корзины и сворачивает каждую корзину в одну строку. HAVING существует по одной причине: WHERE не может видеть результаты агрегатных функций, потому что агрегация происходит после фильтрации. Это не баг стандарта SQL - это следствие порядка выполнения операций.
**Правило GROUP BY:** в SELECT можно использовать только колонки из GROUP BY или агрегатные функции. PostgreSQL строго соблюдает это правило. MySQL исторически разрешал нарушения - результат был непредсказуемым.
**A/B тест через SQL:** `SELECT variant, COUNT(*) AS users, AVG(metric) AS avg_metric FROM experiment_events GROUP BY variant HAVING COUNT(*) > 100` - стандартный паттерн для оценки статистической значимости без Python.
Почему `WHERE COUNT(*) > 5` вызывает ошибку, а `HAVING COUNT(*) > 5` работает?
Подзапросы: запросы внутри запросов
2004 год. Facebook только запустился. Нужно найти пользователей, у которых больше друзей, чем средний показатель по сети. Написать это в одном SELECT невозможно - сначала нужно вычислить среднее, потом сравнить. Подзапрос решает именно эту задачу: результат одного запроса становится условием или источником для другого.
**Некоррелированный vs коррелированный:** некоррелированный выполняется один раз (быстро). Коррелированный выполняется для каждой строки внешнего запроса (медленно на больших таблицах - заменять на JOIN или оконные функции).
Коррелированный подзапрос выполняется:
Операции над множествами: UNION, INTERSECT, EXCEPT
Теория множеств Кантора 1874 года плюс реляционная алгебра Кодда 1970-го - и UNION, INTERSECT, EXCEPT становятся не синтаксическим сахаром, а математически строгими операциями. Разработчики забывают об одном: UNION по умолчанию удаляет дубликаты, и для этого сортирует весь результат. UNION ALL пропускает этот шаг. Разница в производительности на миллионах строк - существенная.
**UNION vs UNION ALL:** если дубликаты допустимы или заведомо невозможны - всегда использовать UNION ALL. UNION сортирует весь результат для удаления дубликатов, что добавляет O(n log n) к запросу.
**ORDER BY в UNION:** нельзя добавить ORDER BY к каждому подзапросу. ORDER BY пишется один раз в конце всего UNION-запроса и сортирует финальный результат.
UNION работает как append в Python - просто склеивает строки
UNION - операция над множествами с удалением дубликатов. UNION ALL - append. Для чистого склеивания нужен именно UNION ALL
В теории множеств объединение A ∪ B содержит уникальные элементы. SQL следует этой математике. UNION ALL - расширение стандарта для производительности, когда уникальность не нужна
UNION и UNION ALL объединяют [1,2,3] и [2,3,4]. Что вернёт каждый?
SQL: SELECT, JOIN, GROUP BY
- WHERE выполняется до SELECT - алиасы недоступны, агрегаты невозможны
- LEFT JOIN сохраняет все строки основной таблицы (NULL там где нет совпадения)
- HAVING фильтрует группы после GROUP BY - единственное место для агрегатов в условии
- UNION удаляет дубликаты через сортировку; UNION ALL быстрее когда уникальность не нужна
Связанные темы
SQL - язык, реляционная модель - математика за ним. Индексы определяют скорость выполнения запросов.
- Реляционная модель — Математическая основа - таблицы, ключи, нормализация
- Индексы (B-Tree) — Определяют скорость SELECT и JOIN на больших таблицах
- ACID транзакции — Гарантии консистентности при параллельных запросах
- Big-O сложность — Оценка сложности JOIN и GROUP BY по числу строк
Вопросы для размышления
- GROUP BY разбивает таблицу на корзины и агрегирует каждую. Что происходит с порядком строк внутри корзины - какое значение вернёт MAX при наличии нескольких одинаковых максимальных?
- Коррелированный подзапрос выполняется N раз (по числу строк). При каком условии EXISTS быстрее IN, а при каком - медленнее?
- UNION удаляет дубликаты через сортировку результата. Меняет ли это итоговый ORDER BY и почему нельзя сортировать подзапросы внутри UNION?