Базы данных

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?

Связанные уроки

  • alg-20-greedy
SQL: SELECT, JOIN, GROUP BY

0

1

Войти