Codd 1970: relation, tuple, attribute, domain
В предыдущих модулях вы рисовали ER-диаграммы: сущности, атрибуты, связи. Это удобный способ думать о данных, но ER-диаграмма — это не математика. Её нельзя «вычислить», нельзя доказать, что запрос к ней корректен. Реляционная модель — это другое. Это формальная теория, в которой таблица — это математический объект, а запрос — это операция над этим объектом с предсказуемым результатом.
В 1970 году сотрудник IBM Edgar F. Codd опубликовал статью «A Relational Model of Data for Large Shared Data Banks». До неё базы данных были иерархическими (дерево) или сетевыми (граф): чтобы достать запись, программист вручную писал навигацию по указателям. Codd предложил радикальную идею: отделить логическое представление данных от физического хранения. Пользователь описывает, ЧТО он хочет получить, в терминах множеств и отношений; СУБД сама решает, КАК это достать с диска. Эта идея — фундамент SQL и всех реляционных СУБД, которыми мы пользуемся сегодня.
Этот урок — про четыре базовых термина модели Codd. Их легко спутать с «таблица, строка, столбец, тип данных» — и в первом приближении это верно, но различия принципиальны, и без точных определений вы не поймёте, почему дальше работают ключи, нормализация и реляционная алгебра.
Domain: множество допустимых значений
Начнём не с relation, а с domain — потому что domain первичен, всё остальное строится на нём.
Domain — это именованное множество допустимых значений одного типа. Не «тип данных» в смысле языка программирования, а именно множество конкретных значений, которые имеют смысл для предметной области.
Примеры:
- Domain
EmailAddress— множество всех синтаксически корректных email-адресов. - Domain
AgeYears— множество целых чисел от 0 до 150. - Domain
CurrencyCode— множество строк из трёх букв:{USD, EUR, RUB, JPY, ...}. - Domain
OrderStatus— множество{new, paid, shipped, cancelled}.
Domain отвечает на вопрос «какие значения вообще допустимы для этого атрибута». Тип данных СУБД (INTEGER, VARCHAR(3)) — лишь грубое приближение domain. VARCHAR(3) разрешает строку 'XYZ', но в domain CurrencyCode её нет. Поэтому в реальной схеме domain доуточняют через CHECK-constraint или ENUM — об этом будет отдельный урок про constraints.
Ключевое свойство domain: значения из разных domains несравнимы, даже если физически это одинаковый тип. Если age определён над domain AgeYears, а shoe_size — над ShoeSizeEU, то сравнение age = shoe_size в строгой реляционной модели бессмысленно — это разные множества. SQL это правило ослабляет (сравнит два INTEGER без вопросов), и именно поэтому SQL иногда позволяет писать запросы, которые синтаксически валидны, но семантически абсурдны.
Attribute, tuple, relation
Теперь три остальных термина — снизу вверх.
Attribute — это пара «имя + domain». Например, attribute email определён над domain EmailAddress. Attribute — это столбец, но важно: столбец опознаётся по имени, а не по позиции. В реляционной модели нет «третьего столбца», есть только «столбец email».
Tuple — это строка: набор пар «имя атрибута -> значение», по одному значению на каждый attribute relation. Формально tuple — это функция, которая каждому имени атрибута сопоставляет значение из соответствующего domain. Запись tuple:
{ user_id: 42, email: '[email protected]', age: 29 }
Поскольку tuple — это набор именованных пар, порядок пар в нём не имеет значения. Записать тот же tuple как { age: 29, user_id: 42, email: '[email protected]' } — это буквально тот же самый tuple.
Relation — это множество tuples, у которых одинаковый набор атрибутов. Слово «множество» (set) здесь — не метафора, а точный термин из теории множеств, и из него следуют все остальные свойства, которые мы разберём в следующем уроке: в множестве нет дубликатов и нет порядка элементов.
У relation две части:
- Heading (заголовок) — фиксированный набор атрибутов (имена + domains). Это схема relation, она не меняется от данных.
- Body (тело) — множество tuples, соответствующих heading. Это и есть данные; они меняются при каждом INSERT/UPDATE/DELETE.
Relation против таблицы: в чём подвох
Слово «таблица» в SQL и слово «relation» в теории — почти синонимы, но «почти» здесь важно. Relation — это идеализированный математический объект. Таблица SQL — его инженерная реализация, в которой ради практичности сделаны послабления.
| Аспект | Relation (теория Codd) | Таблица (SQL) |
|---|---|---|
| Дубликаты строк | Запрещены (это множество) | Разрешены, если нет PRIMARY KEY или UNIQUE |
| Порядок строк | Не существует | Физический порядок есть; SELECT без ORDER BY его не гарантирует |
| Порядок столбцов | Не существует | Есть; SELECT * возвращает столбцы в порядке CREATE TABLE |
| Безымянные столбцы | Невозможны | Возможны: SELECT count(*) даёт столбец без имени |
| NULL | В исходной модели Codd отсутствовал | Есть, и ломает многое (отдельный урок) |
Главный практический вывод: таблица без PRIMARY KEY — это не relation, а «мешок» (bag, multiset), в котором могут лежать одинаковые строки. Реляционная теория к такому мешку неприменима: рассуждения о ключах, нормализации, корректности декомпозиции опираются на то, что строки уникальны. Поэтому правило «у каждой таблицы должен быть первичный ключ» — это не стилистический совет, а условие, при котором таблица вообще остаётся реляционным объектом.
-- Это relation: строки гарантированно уникальны
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
email TEXT NOT NULL,
age INTEGER
);
-- Это НЕ relation, а bag: можно вставить две одинаковые строки
CREATE TABLE log_lines (
message TEXT
);
INSERT INTO log_lines VALUES ('ping'), ('ping');
SELECT count(*) FROM log_lines;
-- count
-- -----
-- 2 <- два идентичных tuple; в relation такого быть не может
Codd ввёл термины relation/tuple/attribute сознательно, чтобы оторвать мышление инженера от «таблицы как куска памяти». «Таблица» наводит на мысль о ячейках, координатах, позициях. «Relation» — о множестве фактов. Разные слова — разный способ думать о данных.
Почему модель построена на множествах
Зачем Codd взял именно теорию множеств? Потому что у множеств есть готовый, проверенный веками аппарат операций: объединение, пересечение, разность, декартово произведение. Если relation — это множество, то к двум relations можно применять эти операции и получать новый relation. Это и есть реляционная алгебра (следующие уроки), а реляционная алгебра — это математический фундамент SQL.
Свойство, ради которого всё затевалось, называется замкнутость (closure): результат любой операции над relation(s) — это снова relation. Из замкнутости следует, что операции можно вкладывать друг в друга без ограничений: результат одного SELECT подать на вход другому. Подзапросы, CTE, представления (views) — всё это работает именно потому, что модель замкнута. Если бы операция над таблицами иногда возвращала «не таблицу», вложенность была бы невозможна.
Попробуй сам
Возьмите любую знакомую сущность — например, «книга в библиотеке». Выпишите на бумаге:
- Пять атрибутов книги (
isbn,title,pages,published_year,language). - Для каждого атрибута — его domain: точное множество допустимых значений (не тип СУБД, а именно «какие значения имеют смысл»). Для
published_yearэто, например, целые от 1450 до текущего года; дляlanguage— конечный список кодов языков. - Один конкретный tuple — строку с реальными значениями, записанную как набор именованных пар.
- Ответьте: если вы вставите два tuple с одинаковым
isbn— это всё ещё relation? Почему нет, и какой constraint это запрещает?
Затем создайте таблицу books в SQLite или PostgreSQL с PRIMARY KEY и убедитесь, что СУБД отклоняет вставку дубликата по ключу. Это и есть граница между «relation» и «bag» на практике.