Learning Platform
Урок 03.06 · 16 мин
Начальный
Algebra decompositionQuery readingCapstone

Мы прошли четыре операции реляционной алгебры — σ, π, ⨯/⋈, ∪/∩/\\ — и увидели, как SQL прячет их за WHERE, SELECT, JOIN, UNION. Время сложить всё вместе.

В этом уроке возьмём один реалистичный аналитический запрос и разложим его на цепочку алгебраических операций. Эта привычка — «читать SQL как алгебру» — главное умение, которое отличает уверенного middle от человека, который пишет запросы методом «копи-паст-подбора».

Задача

Найди уникальные пары (email, country) клиентов из России или Германии, у которых был хотя бы один заказ в статусе paid.

Прежде чем смотреть SQL — попробуй сформулировать словами, какие операции нам понадобятся. Пауза. Подумай.

Вот моё разложение:

  1. σ на customers: отобрать только тех, у кого country IN ('RU', 'DE').
  2. σ на orders: отобрать только заказы со статусом paid.
  3. (equi-join) этих двух выборок по customers.id = orders.customer_id.
  4. π на результате: оставить только email и country.
  5. DISTINCT — превратить bag-проекцию в множество.

В виде формулы:

πemail,country(σcountry{RU,DE}(customers)σstatus=paid(orders))\pi_{email, country}\big(\sigma_{country \in \{RU,DE\}}(\text{customers}) \bowtie \sigma_{status='paid'}(\text{orders})\big)

В виде SQL:

Тот же самый запрос. Глянь результат и сверь со своим ответом:

PostgreSQL

Дерево алгебры

Дерево операций для нашего запроса

Читается снизу вверх: листья — таблицы, корень — результат.

кореньπ[email, country] + DISTINCT
⋈ ON c.id = o.customer_id
левая ветвьσ[country IN (RU,DE)] (customers)
листcustomers (12 строк)
правая ветвьσ[status='paid'] (orders)
листorders (20 строк)

Заметь, что обе селекции в моём разложении применяются ДО соединения. Это и есть push-down — каждый из фильтров не зависит от данных другой таблицы, значит, его можно «протолкнуть» как можно ниже. После такого разложения в JOIN идут уже меньшие выборки (~9 customers и ~3-4 orders вместо 12 и 20), и комбинаций становится в разы меньше.

Если бы я записал разложение как σ[...AND...] (customers ⋈ orders) — это тоже корректно алгебраически, но менее эффективно: пришлось бы джойнить полные таблицы и фильтровать только потом. Оптимизатор PostgreSQL в любом случае сам сделает push-down — но привычка «думать пушдауном» помогает писать запросы, в которых оптимизатору ничего не нужно править.

Тренировка

Перепиши тот же запрос через UNION двух выборок (одна для RU, одна для DE), без IN:

PostgreSQL

Тот же результат! Алгебраически: σ[country='RU'](...) ∪ σ[country='DE'](...) ≡ σ[country IN ('RU','DE')](...). Это — частный случай алгебраического тождества: дизъюнкция предикатов разворачивается в объединение селекций.

Оптимизатор PostgreSQL знает эти тождества. Если ты напишешь через UNION, он может (но не обязан) переписать в IN — и наоборот. Оба плана работают одинаково на маленьких данных; на больших — обычно IN чуть лучше, потому что не требует дедупликации UNION’ом.

Selectivity: как оптимизатор оценивает WHERE

Что нужно унести из модуля 2

После шести уроков ты:

  1. Знаешь, что реляция — это множество кортежей, и понимаешь следствия: NULL-семантика, отсутствие гарантированного порядка, разница между set и bag.
  2. Узнаёшь в SQL четыре основные операции — σ (WHERE), π (SELECT), ⋈ (JOIN ON), ∪/∩/\\ (UNION/INTERSECT/EXCEPT).
  3. Понимаешь, что SQL декларативный, а СУБД переводит твой запрос в дерево алгебры и оптимизирует через перестановки операций (push-down, reorder JOIN).

Это база, к которой мы будем возвращаться каждый раз, когда увидим новый SQL-конструкт. JOIN’ы из модуля 5, агрегации из модуля 6, оконные функции из модуля 8 — всё это надстройка над теми же операциями. Понимая фундамент, ты не будешь учить SQL «по рецептам» — ты будешь видеть, что любой запрос — это просто комбинация шести базовых кирпичей.

В следующем модуле углубимся в одну специфическую тему, которая красной нитью идёт через весь модуль 2 — типы данных и NULL-семантика. Разберём, почему PostgreSQL хранит числа в одной из шести разных представлений, почему '1' = 1 иногда работает, а иногда нет, и почему IS DISTINCT FROM — твой друг при работе с NULL.

Проверка знанийKnowledge check
Напиши без помощи компьютера: алгебраическое разложение для запроса «SELECT name FROM products WHERE category_id = 5 AND in_stock > 0».
ОтветAnswer
π[name] ( σ[category_id=5 AND in_stock>0] ( products ) ). Два предиката в WHERE — это одна селекция с конъюнкцией; селекция применяется до проекции (порядок здесь не критичен, потому что предикат не использует выкинутых атрибутов). Альтернативное эквивалентное разложение: π[name] ( σ[in_stock>0] ( σ[category_id=5] ( products ) ) ) — два последовательных σ дают тот же результат.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Дан запрос: ```sql SELECT DISTINCT c.country FROM customers c JOIN orders o ON c.id = o.customer_id WHERE o.status = 'delivered'; ``` Какое разложение на реляционную алгебру корректно описывает этот запрос?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6