Learning Platform
Глоссарий Troubleshooting
Урок 04.02 · 19 мин
Начальный
er-modelingattributescomposite-attributederived-attribute

Типы атрибутов: simple, composite, multi-valued, derived

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

Этот урок разбирает четыре оси классификации атрибутов. Понимать их важно, потому что именно здесь начинается связь ER-модели с реляционными правилами: например, многозначный атрибут — это прямой повод задуматься о нормальной форме, которую мы разберём позже.

Ось 1: simple vs composite

Простой атрибут (simple, atomic) — атрибут, который нельзя осмысленно разбить на более мелкие части. «Возраст», «цена», «email» как единое значение — простые атрибуты.

Составной атрибут (composite) — атрибут, состоящий из нескольких компонентов, каждый из которых имеет собственный смысл. Классический пример — «полное имя»: оно делится на фамилию, имя, отчество. Или «адрес»: страна, город, улица, дом, индекс.

Ключевой вопрос — нужны ли компоненты по отдельности. Если по «полному имени» когда-нибудь понадобится отсортировать по фамилии или обратиться к имени отдельно — это составной атрибут, и его компоненты лучше хранить раздельными колонками. Если адрес используется только целиком (показать на экране, напечатать на конверте) — можно хранить одной строкой.

Составной атрибут и его компоненты
Составной: ФИОСоставной атрибут — состоит из нескольких компонентов, каждый со своим смыслом. На физическом уровне обычно разбивается на отдельные колонки.
раскладывается на
Простые: фамилия, имя, отчествоКомпоненты составного атрибута. Каждый — простой атрибут, его уже нельзя осмысленно разбить дальше.

При переходе к таблице составной атрибут обычно разбивают на колонки по компонентам: вместо одной колонки full_name — три колонки last_name, first_name, middle_name. Это даёт гибкость: можно фильтровать и сортировать по любому компоненту.

-- Составной атрибут "ФИО", разбитый на компоненты:
CREATE TABLE customers (
    customer_id  BIGINT PRIMARY KEY,
    last_name    TEXT NOT NULL,
    first_name   TEXT NOT NULL,
    middle_name  TEXT
);
-- Теперь легко: SELECT * FROM customers ORDER BY last_name;

Ось 2: single-valued vs multi-valued

Однозначный атрибут (single-valued) — у одного экземпляра сущности может быть только одно значение этого атрибута. Дата рождения у человека одна. Год выпуска у автомобиля один.

Многозначный атрибут (multi-valued) — у одного экземпляра может быть несколько значений одновременно. У клиента может быть несколько телефонов. У человека — несколько гражданств. У товара — несколько фотографий.

Многозначный атрибут — самый коварный вид, потому что реляционная таблица не умеет хранить несколько значений в одной ячейке. Соблазн велик — записать телефоны через запятую в одну колонку:

-- ПЛОХО: многозначный атрибут списком в одной ячейке
CREATE TABLE customers_bad (
    customer_id BIGINT PRIMARY KEY,
    phones      TEXT   -- "+7900..., +7901..., +7902..."
);

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

-- ХОРОШО: многозначный атрибут вынесен в отдельную таблицу
CREATE TABLE customers (
    customer_id BIGINT PRIMARY KEY,
    name        TEXT NOT NULL
);

CREATE TABLE customer_phones (
    customer_id BIGINT NOT NULL REFERENCES customers(customer_id),
    phone       TEXT   NOT NULL,
    phone_type  TEXT,  -- 'мобильный', 'рабочий'
    PRIMARY KEY (customer_id, phone)
);
-- Один клиент -> много строк в customer_phones. Каждый телефон атомарен.
WARNING

Список значений через запятую в одной колонке — один из самых частых анти-паттернов начинающих. Он кажется удобным («всё в одном месте»), но ломает почти любую работу с этими значениями. Увидев многозначный атрибут, сразу планируйте отдельную таблицу. Подробно про 1NF и атомарность — в модуле про нормализацию.

Ось 3: stored vs derived

Хранимый атрибут (stored) — значение хранится в базе как есть. Дата рождения хранится.

Вычисляемый атрибут (derived) — значение не хранится, а вычисляется из других атрибутов в момент запроса. Возраст человека — вычисляемый: он выводится из даты рождения и текущей даты. Возраст не нужно хранить — он устаревал бы каждый день.

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

-- Дата рождения хранится; возраст вычисляется в запросе, не хранится:
SELECT
    name,
    birth_date,
    date_part('year', age(birth_date)) AS current_age
FROM customers;
-- Колонки current_age в таблице нет — она вычисляется каждый раз.

Другие примеры вычисляемых атрибутов: сумма заказа (из цен и количеств позиций), количество заказов клиента (подсчётом строк), полное имя (конкатенацией компонентов).

Хранимый и вычисляемый атрибут
Хранимое: дата рожденияХранимый атрибут — значение лежит в базе как есть. Источник истины, из него выводятся другие.
вычисляется из него
Вычисляемое: возрастВычисляемый атрибут — не хранится, а считается из даты рождения и текущей даты в момент запроса. Хранить нельзя: устареет.
NOTE

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

Ось 4: key attribute (идентификатор)

Отдельно стоит ключевой атрибут (key attribute, identifier) — атрибут (или набор атрибутов), который уникально идентифицирует экземпляр сущности. У сущности Клиент таким может быть customer_id или email. Никакие два разных клиента не имеют одинакового значения ключевого атрибута.

Ключевой атрибут — не «ещё один вид» в одном ряду с предыдущими осями, а особая роль: ключом может быть простой атрибут (customer_id), а может быть составной (например, для сущности «позиция заказа» ключ — пара «заказ + товар»). На ER-диаграммах ключевой атрибут принято выделять — подчёркиванием или пометкой.

Ключи — большая тема, им посвящён целый модуль курса (superkey, candidate key, primary key, natural и surrogate keys). Здесь важно зафиксировать: у любой сущности обязательно есть чем уникально отличить её экземпляры, и этот атрибут выделяется среди остальных.

Сводка: четыре оси классификации

Один и тот же атрибут классифицируется сразу по всем осям. «Дата рождения» — простой, однозначный, хранимый, не ключевой. «Список телефонов» — простой по структуре, но многозначный. Соберём оси в таблицу.

ОсьВариантыЧем определяется выбор
Структураsimple / compositeНужны ли компоненты по отдельности
Количество значенийsingle-valued / multi-valuedМожет ли быть несколько значений у одного экземпляра
Источник значенияstored / derivedВыводимо ли значение из других данных
Рольобычный / key (идентификатор)Идентифицирует ли уникально экземпляр

Главная мысль: вид атрибута — это не теоретическая метка, а решение о структуре. Составной атрибут влияет на число колонок. Многозначный требует отдельной таблицы. Вычисляемый не хранится вообще. Ключевой определяет идентификацию строки.

Массивы в PostgreSQL — альтернатива отдельной таблице для multi-valued Типы данных и домен атрибута — физический уровень

Почему это влияет на физическую схему

Свяжем урок с предыдущими модулями. На logical-уровне модели вы классифицируете каждый атрибут по четырём осям. На physical-уровне эта классификация превращается в конкретные решения:

  • составной атрибут -> несколько колонок;
  • многозначный атрибут -> отдельная таблица со связью один-ко-многим;
  • вычисляемый атрибут -> не колонка, а выражение в запросе или вычисляемая колонка;
  • ключевой атрибут -> объявляется как PRIMARY KEY.

Особенно важна связь многозначного атрибута с нормализацией. Многозначный атрибут, оставленный списком в ячейке, — это нарушение 1NF, первой и самой базовой нормальной формы. Поэтому правильная классификация атрибутов на этапе ER-моделирования — это профилактика проблем нормализации ещё до того, как мы дойдём до неё формально.

Как вид атрибута определяет физическую структуру
CompositeСоставной атрибут на физическом уровне обычно разбивается на несколько отдельных колонок по компонентам.
Multi-valuedМногозначный атрибут выносится в отдельную таблицу со связью один-ко-многим. Список в ячейке недопустим — нарушение 1NF.
DerivedВычисляемый атрибут не хранится колонкой — он вычисляется выражением в запросе. Хранение порождает риск рассинхрона.

Попробуй сам

Возьмите сущность Сотрудник и набор её атрибутов: ФИО, дата рождения, стаж работы (в годах), email, навыки (человек владеет несколькими), оклад, табельный номер. Для каждого атрибута определите его вид по всем четырём осям: простой или составной, однозначный или многозначный, хранимый или вычисляемый, является ли ключевым. Особо разберите два: «стаж работы» (подумайте, хранить или вычислять, и из чего) и «навыки» (это многозначный атрибут — как его правильно представить). Затем напишите CREATE TABLE для сущности Сотрудник плюс, если нужно, дополнительную таблицу — так, чтобы ни один многозначный атрибут не лежал списком в ячейке и ни один вычисляемый не хранился зря.


Проверка знанийKnowledge check
Что такое многозначный (multi-valued) атрибут, почему хранить его списком через запятую в одной колонке — ошибка, и как его моделировать правильно?
ОтветAnswer
Многозначный (multi-valued) атрибут — это атрибут, у которого один экземпляр сущности может иметь несколько значений одновременно: например, у клиента несколько телефонов, у человека несколько гражданств, у товара несколько фотографий. Хранить такой атрибут списком через запятую в одной колонке — ошибка, потому что это нарушает атомарность и первую нормальную форму (1NF): реляционная ячейка должна содержать одно атомарное значение. Список в ячейке делает невозможной нормальную работу с этими значениями — нельзя эффективно искать по отдельному телефону, посчитать их количество, проверить формат каждого, наложить ограничение. Правильное решение — вынести многозначный атрибут в отдельную таблицу со связью один-ко-многим: например, таблица customer_phones, где каждому телефону клиента соответствует отдельная строка, ссылающаяся на клиента через внешний ключ. Тогда каждое значение атомарно, и с телефонами можно полноценно работать. Список в ячейке — один из самых частых анти-паттернов начинающих, и правильная классификация атрибута на этапе ER-моделирования предотвращает эту проблему ещё до формальной нормализации.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Атрибут «полное имя», состоящий из фамилии, имени и отчества, к какому виду относится и как его обычно реализуют в таблице?

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

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

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

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