Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 17 мин
Начальный
relational-modelrelation-propertiesatomicitycardinality

Свойства relation: неупорядоченность, уникальность, атомарность

В прошлом уроке мы дали определение: relation — это множество tuples с одинаковым heading. Слово «множество» (set) — не украшение. Из него математически вытекает несколько свойств, и каждое из них имеет прямое практическое последствие при работе с SQL. Если их не понимать, вы будете писать запросы, которые «иногда работают» — а это худший вид кода в данных.

Разберём четыре свойства relation и две метрики его размера. Всё это — прямые следствия определения, а не отдельные правила, которые надо заучивать.

Свойство 1: строки неупорядочены

В relation нет порядка строк. Множество {1, 2, 3} и множество {3, 1, 2} — это одно и то же множество; точно так же relation не помнит, в каком порядке tuples были добавлены.

Практическое последствие: запрос SELECT без ORDER BY не даёт никаких гарантий порядка строк. Не «обычно возвращает в порядке вставки», не «как правило по первичному ключу» — никаких гарантий вообще. Сегодня СУБД вернёт строки в одном порядке, завтра — после обновления статистики, смены плана запроса или просто параллельного чтения — в другом.

-- Опасный код: порядок не гарантирован
SELECT name FROM users LIMIT 3;
-- Сегодня вернёт Ann, Bob, Cy
-- Завтра может вернуть Cy, Ann, Bob — план запроса изменился

-- Корректный код: порядок задан явно
SELECT name FROM users ORDER BY name LIMIT 3;
-- Всегда Ann, Bob, Cy

Если результат должен быть упорядочен — пишите ORDER BY. Всегда. Это не паранойя: это единственный способ заставить реляционную СУБД дать вам порядок, потому что в самой модели порядка не существует.

Свойство 2: столбцы неупорядочены

Аналогично строкам, столбцы relation тоже не имеют порядка. Attribute опознаётся по имени, а «третьего столбца» в реляционной модели не существует.

Практическое последствие: не полагайтесь на порядок столбцов. Две конкретные ошибки:

-- Плохо: SELECT * — порядок столбцов зависит от CREATE TABLE,
-- а кто-то может пересоздать таблицу с другим порядком
SELECT * FROM users;

-- Плохо: INSERT без списка столбцов — привязка по позиции
INSERT INTO users VALUES (42, '[email protected]', 29);
-- Если в таблицу добавят столбец, этот INSERT молча сломается

-- Хорошо: явные имена столбцов
INSERT INTO users (user_id, email, age) VALUES (42, '[email protected]', 29);
SELECT user_id, email FROM users;

SELECT * и INSERT ... VALUES без списка столбцов работают «по позиции» — а позиция в реляционной модели не определена. Явные имена столбцов делают код устойчивым к изменениям схемы.

Свойство 3: нет дублирующихся строк

Множество не содержит одинаковых элементов. Значит, в relation не может быть двух полностью идентичных tuples.

Из этого следует важнейший факт: в любом relation существует набор атрибутов, который уникально идентифицирует каждую строку. В худшем случае это все атрибуты сразу (раз строки различны, полный набор атрибутов их различает). На практике почти всегда есть меньший набор — это и есть ключ (следующий модуль курса целиком про ключи).

SQL, как мы видели, разрешает дубликаты, если у таблицы нет PRIMARY KEY/UNIQUE. Поэтому правило простое: объявляйте первичный ключ, и тогда таблица остаётся настоящим relation, к которому применима вся теория.

Свойство 4: каждое значение атомарно

Это самое тонкое свойство. Атомарность значит: значение в ячейке (на пересечении tuple и attribute) — одно и неделимое с точки зрения модели. В ячейке не может лежать список, массив, вложенная таблица или несколько значений «через запятую».

Это требование называется первой нормальной формой (1NF), и в модуле про нормализацию мы вернёмся к нему подробно. Сейчас важно понять, что нарушает атомарность.

НЕ атомарно (нарушение 1NF):

order_id | items
---------+----------------------------
   1001  | 'apple, banana, cherry'      <- три значения в одной ячейке
   1002  | 'milk'

Чтобы посчитать товары в заказе 1001, СУБД пришлось бы
парсить строку. Реляционные операторы так не умеют.


Атомарно (1NF соблюдена):

order_id | item
---------+---------
   1001  | apple
   1001  | banana
   1001  | cherry
   1002  | milk

Теперь "сколько товаров в заказе 1001" — это COUNT(*),
а не разбор строки.

Почему атомарность критична? Реляционная алгебра — операторы select, project, join — работает со значениями как с чёрными ящиками: она их сравнивает на равенство, но не «заглядывает внутрь». Если в ячейке лежит 'apple, banana, cherry', то условие WHERE item = 'banana' его не найдёт — для СУБД это одна строка 'apple, banana, cherry', не равная 'banana'. Хранение списка через запятую — один из самых частых антипаттернов новичков, и он ломает ровно то, ради чего существует реляционная модель.

WARNING

«Атомарно» не значит «нельзя разбить вообще никак». Дату 2026-05-20 можно разбить на год-месяц-день, и это нормально хранить как одно значение типа DATE. Атомарность — про то, что МОДЕЛЬ обращается со значением как с одним целым. Список из трёх товаров — это три независимых факта, и модель должна видеть три строки. Дата — один факт, одна ячейка.

Четыре свойства relation как следствия 'множества'
Relation = множество tuplesИсходное определение. Все четыре свойства ниже — его прямые математические следствия.
следствия
Нет порядка строкМножество не упорядочено. Следствие: SELECT без ORDER BY не гарантирует порядок.
Нет порядка столбцовАтрибут опознаётся по имени. Следствие: не полагайся на SELECT * и INSERT по позиции.
Нет дубликатов строкМножество не содержит одинаковых элементов. Следствие: всегда существует ключ.
Атомарность значенийЗначение неделимо для модели. Следствие: никаких списков через запятую (1NF).

Degree и cardinality: две метрики размера

У relation есть два числа, описывающих его размер. Их легко перепутать, поэтому запомните точно.

Degree (степень) — это число атрибутов relation, то есть «ширина» таблицы. Degree определяется heading-ом и не зависит от данных: пустая таблица с тремя столбцами имеет degree 3. Relation degree 1 называют унарным, degree 2 — бинарным, degree n — n-арным.

Cardinality (мощность) — это число tuples в relation, то есть «высота» таблицы. Cardinality определяется body и меняется при каждом INSERT/DELETE. Пустой relation имеет cardinality 0.

CREATE TABLE users (
    user_id INTEGER PRIMARY KEY,
    email   TEXT,
    age     INTEGER
);
-- degree = 3 (три атрибута), cardinality = 0 (нет строк)

INSERT INTO users VALUES (1, '[email protected]', 20), (2, '[email protected]', 31);
-- degree = 3 (не изменился), cardinality = 2

-- cardinality считается так:
SELECT count(*) FROM users;
-- count
-- -----
--     2
МетрикаЧто измеряетЧем определяетсяМеняется при INSERT?
Degreeчисло атрибутов (ширина)heading (схема)Нет
Cardinalityчисло tuples (высота)body (данные)Да
Degree и cardinality: ширина и высота relation
Heading -> degreeHeading — набор атрибутов, то есть схема. Он задаёт degree (число столбцов). От данных не зависит, при INSERT не меняется.
heading фиксирован, body растёт
Body -> cardinalityBody — множество tuples, то есть данные. Оно задаёт cardinality (число строк). Меняется при каждом INSERT и DELETE.

Зачем это знать junior-инженеру? Слово cardinality встретится ещё много раз и в разных смыслах. В этом уроке cardinality — это число строк в таблице. В модуле про связи «кардинальность связи» (1:1, 1:N, M:N) — это про то, сколько строк одной таблицы соответствует строке другой. А ещё оптимизатор запросов оценивает «cardinality» промежуточных результатов, чтобы выбрать план. Контекст всегда подскажет, но базовое значение — «количество» — общее.

Почему эти свойства — не формальность

Может показаться, что четыре свойства relation — академическая теория, далёкая от ежедневной работы. На самом деле каждое из них — это конкретная привычка, отличающая инженера, чей код работает стабильно, от того, чей код «работает, пока не сломается».

Связь свойств с практикой прямая. «Нет порядка строк» — это привычка всегда писать ORDER BY, когда порядок важен, и никогда не полагаться на «обычно возвращается так». «Нет порядка столбцов» — это привычка писать явные имена столбцов в SELECT и INSERT, чтобы код пережил эволюцию схемы. «Нет дубликатов» — это привычка объявлять первичный ключ у каждой таблицы. «Атомарность» — это привычка не складывать списки в одну ячейку, а заводить отдельную строку на каждый факт.

Все четыре привычки объединяет одно: они защищают от недетерминированного поведения — кода, который даёт разный результат при одинаковых входных данных в зависимости от плана запроса, версии СУБД или порядка вставки. Недетерминированный код в данных особенно коварен: он проходит тесты, работает в разработке, а ломается в production через месяцы — когда таблица выросла, статистика обновилась и оптимизатор сменил план. Понимание свойств relation — это понимание того, на что в реляционной СУБД можно опираться, а на что нельзя.

ORDER BY и SELECT * — свойства relation в SQL-практике
TIP

Хорошая проверка при написании запроса: спросите себя «а что гарантирует мне этот результат?». Если ответ опирается на порядок строк без ORDER BY, на порядок столбцов, на отсутствие дубликатов в таблице без ключа — значит, вы опираетесь на то, чего реляционная модель не обещает. Перепишите так, чтобы корректность следовала из явных конструкций, а не из везения.

Попробуй сам

Создайте в SQLite или PostgreSQL таблицу products с четырьмя столбцами и проделайте небольшой эксперимент:

  1. Сразу после CREATE TABLE, до вставки данных, ответьте: чему равны degree и cardinality? Проверьте cardinality через SELECT count(*).
  2. Вставьте пять строк. Что изменилось — degree, cardinality, оба?
  3. Выполните SELECT * FROM products несколько раз и SELECT * FROM products ORDER BY .... Без ORDER BY порядок может совпадать на маленькой таблице — но объясните себе, почему на это нельзя полагаться в production.
  4. Попробуйте сознательно нарушить атомарность: добавьте столбец tags и впишите туда 'sale, new, popular'. Затем напишите запрос «найди все продукты с тегом new» через WHERE tags = 'new'. Он ничего не найдёт. Объясните почему и как правильно — через отдельную таблицу product_tags со строкой на каждый тег.

Проверка знанийKnowledge check
Что означает свойство атомарности значений в relation, почему его нарушение (например, хранение списка 'apple, banana, cherry' в одной ячейке) ломает работу с данными, и как это исправить?
ОтветAnswer
Атомарность означает, что значение в ячейке relation неделимо с точки зрения модели: реляционные операторы обращаются с ним как с единым целым, сравнивая на равенство, но не заглядывая внутрь. Это требование первой нормальной формы (1NF). Хранение списка 'apple, banana, cherry' в одной ячейке нарушает атомарность, потому что это на самом деле три независимых факта, упакованных в одно значение. Ломается это так: реляционная алгебра сравнивает значения целиком, поэтому условие WHERE item = 'banana' не найдёт строку, где в ячейке лежит 'apple, banana, cherry' — для СУБД это одно значение, не равное 'banana'. Чтобы что-то с таким полем сделать, пришлось бы парсить строку, а реляционные операторы этого не умеют. Исправляется это вынесением списка в отдельную таблицу со строкой на каждый элемент: вместо одной строки заказа с тремя товарами через запятую — три строки, по одной на товар. После этого "сколько товаров в заказе" становится обычным COUNT(*), а "найти заказы с товаром banana" — обычным WHERE item = 'banana'.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Запрос `SELECT name FROM users LIMIT 3` без ORDER BY вчера вернул строки в одном порядке, а сегодня — в другом. Почему это нормальное поведение реляционной СУБД?

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

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

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

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