В прошлом уроке мы договорились, что реляция — это множество кортежей. Хорошо, теперь хочется уметь что-то с ней делать. Кодд показал, что почти всё, что нужно для работы с данными, выражается через шесть базовых операций. В этом уроке разбираем две первые — самые частые в любом запросе.
Селекция: σ
Формально:
В SQL это WHERE. Запрос SELECT * FROM customers WHERE country = 'RU' — это буквально σ[country='RU'](customers).
Атрибуты остаются те же. Меняется только число кортежей.
Проверим:
σ[country='DE'](customers) — все клиенты из Германии:
Селекция не меняет схему результата: те же атрибуты, та же арность. Меняется только число кортежей. Это важно: значит, к результату σ можно применять любую другую операцию, ожидающую такую же схему — например, ещё одну σ.
Проекция: π
customers арность 6, то после проекции на один атрибут получится отношение арности 1.
Формально:
В SQL это SELECT col1, col2. Запрос SELECT email, country FROM customers — это π[email, country](customers).
Число кортежей в теории может уменьшиться: множество не допускает дубликатов.
Вот тут и начинается разница между теорией и практикой. Посмотри:
π[country](customers) — а сколько строк ожидаешь?
В чистой реляционной алгебре результат — множество стран без повторов: {RU, DE, IL, GE, US}, всего 5 кортежей. Но PostgreSQL вернул 12 строк, потому что в customers 12 клиентов, и многие из одной страны. Почему?
Почему теория и практика расходятся: DISTINCT
Здесь видно фундаментальный компромисс. В чистой реляционной модели проекция возвращает множество, и оно автоматически без дубликатов. Но удалить дубликаты — дорогая операция: нужно либо отсортировать всё, либо построить hash-таблицу. На таблицах в миллионы строк это секунды CPU и гигабайты памяти.
Поэтому SQL в 1986 году принял прагматичное решение: проекция (SELECT col) по умолчанию возвращает мультимножество — с возможными дубликатами. Хочешь поведения чистой алгебры — пиши явно DISTINCT, и ты платишь за уникализацию только когда это действительно нужно.
То же самое, но с DISTINCT — настоящая проекция:
5 строк, как и положено множеству. Запомни: в SQL SELECT без DISTINCT — это не чистая проекция, это её bag-вариант (от англ. bag — «сумка с возможными повторениями»). Это одна из главных точек расхождения между академической реляционной моделью и тем, как реально работает SQL.
Композиция: σ и π вместе
Селекция и проекция комбинируются. Можно сначала отфильтровать, потом спроецировать, или наоборот — результат будет тот же, если предикат селекции не использует «выкинутых» проекцией атрибутов.
— это «email’ы только тех клиентов, кто из Германии». В SQL:
Композиция σ и π — email немецких клиентов:
Здесь две операции встречаются в одном SQL-запросе. PostgreSQL внутри выполнит их в порядке σ → π — отсортирует кортежи фильтром, потом отдаст только нужные столбцы. Можно было бы сделать в обратном порядке: сначала спроецировать все email’ы, потом… но тогда нужен country для фильтра, а его уже нет. Поэтому если предикат использует атрибут, его нужно сохранить до фильтра.
Эта свобода менять порядок операций называется push-down — оптимизатор СУБД старается «протолкнуть» селекцию как можно ближе к источнику данных, чтобы дальше работать с меньшим объёмом. Мы вернёмся к этой идее в модуле про индексы и EXPLAIN.
Чек-лист
- σ (селекция) — фильтр кортежей по предикату. В SQL это
WHERE. Схема не меняется. - π (проекция) — выбор столбцов. В SQL это
SELECT col1, col2. Арность уменьшается. - В чистой алгебре проекция убирает дубликаты автоматически (множество). В SQL — нет, для этого нужен
DISTINCT. - Композиция σ и π даёт большую часть простых аналитических запросов. Оптимизатор может менять их порядок (push-down).