Learning Platform
Глоссарий Troubleshooting
Урок 07.03 · 17 мин
Начальный
relationshipsoptionalityminimum-cardinalitynot-null

Optionality: обязательное и необязательное участие

В первом уроке модуля мы разобрали кардинальность 1:1, 1:N, M:N — она отвечает на вопрос «сколько строк одной таблицы соответствует строке другой». Но у связи есть и второй параметр, не менее важный: «а обязана ли строка вообще участвовать в связи?». Это optionality (опциональность), и формально это минимальная кардинальность связи.

Кардинальность 1:N говорит про максимум: «у пользователя может быть много заказов» — но может ли быть ноль? Обязан ли каждый пользователь иметь хотя бы один заказ? Это и есть вопрос optionality. Junior-инженер, который проектирует связь, но не задаёт этот вопрос, получит схему, которая разрешает бессмысленные состояния данных — или, наоборот, запрещает нормальные.

Минимальная и максимальная кардинальность

У каждого конца связи есть две границы:

  • Максимальная кардинальность — «сколько максимум»: один или много. Это то, что мы обозначаем как 1 или N в записи 1:N.
  • Минимальная кардинальность — «сколько минимум»: ноль или один. Это и есть optionality.

Минимальная кардинальность принимает ровно два значения:

  • Mandatory (обязательное участие), минимум 1 — строка обязана участвовать в связи. Не может быть строки без связанной.
  • Optional (необязательное участие), минимум 0 — строка может участвовать, а может и нет. Допустима строка без связанной.

Полная характеристика конца связи — это пара «минимум..максимум». Стандартные обозначения:

0..1   optional,  максимум один   (ноль или один)
1..1   mandatory, максимум один   (ровно один)
0..N   optional,  максимум много  (ноль или больше)
1..N   mandatory, максимум много  (один или больше)

В нотации Crow’s Foot (вы видели её в модуле про ER-моделирование) optionality показывается символом у конца линии: кружок = optional (ноль допустим), чёрточка = mandatory (минимум один).

Связь характеризуется минимумом И максимумом
Максимальная кардинальностьСколько максимум: один или много. Это 1 или N в записи 1:N. Определяет, нужна ли junction table.
плюс
Минимальная кардинальность (optionality)Сколько минимум: ноль или один. Mandatory (минимум 1) или optional (минимум 0). Определяет NULL-допустимость FK.

Optionality на практике: разбираем связь по сторонам

Optionality задаётся для каждого конца связи отдельно — две стороны могут быть разными. Разберём связь users 1:N orders с двух сторон.

Сторона orders (заказ -> пользователь). Может ли существовать заказ без пользователя? Нет — заказ всегда кем-то сделан, «ничей» заказ бессмыслен. Значит, со стороны заказа участие mandatory: у каждого заказа обязан быть пользователь.

Сторона users (пользователь -> заказы). Может ли существовать пользователь без единого заказа? Да — человек зарегистрировался, но ещё ничего не купил. Это нормальное, валидное состояние. Значит, со стороны пользователя участие optional: у пользователя может быть ноль заказов.

Итог: одна связь, но разная optionality на концах. Со стороны orders — обязательно (1..1: ровно один пользователь). Со стороны users — необязательно (0..N: ноль или больше заказов).

users  ----0..N----  orders  ----1..1----  (обратно к users)

users -> orders:  optional  (пользователь может иметь 0 заказов)
orders -> users:  mandatory (заказ обязан иметь 1 пользователя)

Как optionality реализуется в SQL

Вот где optionality становится конкретным кодом. Минимальная кардинальность со стороны «многих» (то есть на дочерней таблице, где лежит foreign key) реализуется через NULL-допустимость столбца foreign key.

Mandatory участие -> foreign key объявлен NOT NULL. Если заказ обязан иметь пользователя, то столбец orders.user_id не может быть пустым. NOT NULL это и гарантирует.

Optional участие -> foreign key допускает NULL. Если связь со стороны дочерней таблицы необязательна, столбец FK может быть NULL — NULL означает «связи нет».

-- Заказ ОБЯЗАН иметь пользователя: mandatory -> NOT NULL
CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY,
    user_id  INTEGER NOT NULL REFERENCES users(user_id),  -- mandatory
    amount   NUMERIC(12,2) NOT NULL
);
-- Вставить заказ без user_id невозможно — NOT NULL не позволит.

-- Сотрудник МОЖЕТ не иметь руководителя (директор): optional -> NULL разрешён
CREATE TABLE employees (
    employee_id INTEGER PRIMARY KEY,
    name        TEXT NOT NULL,
    manager_id  INTEGER REFERENCES employees(employee_id)  -- optional, NULL OK
);
-- У директора manager_id = NULL — связи с руководителем нет, это нормально.

Запомните соответствие — оно прямое и его легко применять:

Optionality (минимальная кардинальность)Реализация для FK на дочерней таблице
Mandatory (минимум 1)FOREIGN KEY объявлен NOT NULL
Optional (минимум 0)FOREIGN KEY допускает NULL
NOTE

Со стороны «одного» (родительской таблицы) optionality в обычной схеме напрямую через NULL не выражается — там нет столбца FK. «У пользователя минимум один заказ» (mandatory со стороны users) обычным NOT NULL не обеспечить: пользователь и его первый заказ создаются разными INSERT, и между ними пользователь временно без заказов. Такое правило либо реализуют сложнее (отложенная проверка, триггер), либо признают, что в реляционной схеме оно соблюдается на уровне приложения. Поэтому на практике mandatory-минимум чаще всего реально работает именно со стороны дочерней таблицы — через NOT NULL на FK.

Почему optionality — важное проектное решение

Может показаться, что optionality — мелочь: «ну NULL или NOT NULL, какая разница». Разница большая, и вот почему.

Optionality защищает от бессмысленных данных. Если вы забыли поставить NOT NULL на orders.user_id, в таблицу попадут заказы с user_id = NULL — «ничьи» заказы. Любой отчёт «выручка по пользователям» их потеряет (вспомните урок про NULL: JOIN по NULL не находит пару, WHERE отбрасывает). Это тихая потеря данных, которую заметят не сразу.

Optionality влияет на тип JOIN. Если связь со стороны дочерней таблицы mandatory (FK всегда заполнен), то INNER JOIN и LEFT JOIN дадут одинаковый результат — пара найдётся всегда. Если optional (FK бывает NULL), разница принципиальна: INNER JOIN потеряет строки без связи, LEFT JOIN сохранит их с NULL. Понимание optionality подсказывает, какой JOIN писать.

Optionality — это знание о предметной области. «Заказ обязан иметь клиента, сотрудник может не иметь руководителя» — это факты о том, как устроен бизнес. Зафиксировать их в схеме (через NOT NULL или его отсутствие) — то же самое, что зафиксировать constraint: правило переходит из головы разработчика в гарантию СУБД.

LEFT JOIN и NULL — как optionality связи влияет на выбор JOIN
Optionality определяет NULL-допустимость и тип JOIN
Mandatory: FK NOT NULLСвязь обязательна. FK всегда заполнен. Осиротевших строк без связи быть не может.
Optional: FK допускает NULLСвязь необязательна. FK может быть NULL — связи нет. Допустимы строки без пары.
следствие для запросов
Mandatory: INNER = LEFT JOIN. Optional: выбор JOIN критиченПри mandatory пара есть всегда. При optional INNER JOIN теряет строки без связи, LEFT JOIN сохраняет.

Процедура: определить optionality связи

Дополним алгоритм из первого урока. Для связи между A и B, помимо вопросов «сколько максимум» (кардинальность), задайте вопросы про минимум:

  1. Может ли A существовать без связанного B? Да -> со стороны A связь optional. Нет -> mandatory.
  2. Может ли B существовать без связанного A? Да -> со стороны B optional. Нет -> mandatory.
  3. Для конца, где лежит foreign key (дочерняя таблица): mandatory -> объявите FK как NOT NULL; optional -> оставьте FK nullable.

Пример. Связь employees и department (отдел). Может ли сотрудник существовать без отдела? Допустим, по правилам компании — нет, каждый сотрудник приписан к отделу. Значит, со стороны employees участие mandatory -> employees.department_id объявляем NOT NULL. Может ли отдел существовать без сотрудников? Да — новый отдел только создали, людей ещё не набрали. Со стороны department — optional (но это сторона «одного», и через NULL не выражается — см. callout выше).

Попробуй сам

Для каждой связи определите optionality с обеих сторон по процедуре и реализуйте в SQL:

  1. Заказ и Способ оплаты. Может ли заказ существовать без указанного способа оплаты (например, заказ оформлен, но ещё не оплачен)? Решите, mandatory или optional участие со стороны заказа, и реализуйте FK соответственно — NOT NULL или nullable.
  2. Сотрудник и Рабочий ноутбук (1:1). Может ли сотрудник быть без ноутбука? Может ли ноутбук лежать на складе, не закреплённый ни за кем? Опишите optionality обоих концов.
  3. Статья и Автор. Может ли статья существовать без автора? Реализуйте.
  4. Возьмите таблицу orders из примеров урока. Сознательно создайте её БЕЗ NOT NULL на user_id, вставьте один заказ с user_id = NULL, затем напишите INNER JOIN с users и LEFT JOIN с users. Сравните результаты и объясните, какую строку и почему теряет INNER JOIN. Это наглядно показывает, зачем нужна правильная optionality.

Проверка знанийKnowledge check
Что такое optionality (минимальная кардинальность) связи, чем она отличается от обычной кардинальности 1:N, и как mandatory и optional участие реализуются для foreign key в SQL?
ОтветAnswer
Optionality — это минимальная кардинальность связи, то есть ответ на вопрос "обязана ли строка вообще участвовать в связи". Она отличается от обычной кардинальности 1:N тем, что кардинальность 1:N задаёт максимум — "сколько максимум строк одной таблицы соответствует строке другой" (один или много), а optionality задаёт минимум — "сколько минимум" (ноль или один). Это два независимых параметра одной связи: запись 1:N говорит, что у пользователя может быть много заказов, но не говорит, может ли быть ноль — на этот вопрос отвечает именно optionality. Минимальная кардинальность принимает два значения. Mandatory (обязательное участие, минимум 1) — строка обязана участвовать в связи, строки без связанной быть не может; пример: заказ обязан иметь пользователя, "ничей" заказ бессмыслен. Optional (необязательное участие, минимум 0) — строка может участвовать, а может и нет; пример: пользователь может не иметь ни одного заказа — это нормальное состояние. Optionality задаётся для каждого конца связи отдельно, и две стороны могут быть разными. В SQL для foreign key на дочерней таблице optionality реализуется через NULL-допустимость столбца FK: mandatory участие — foreign key объявляется NOT NULL (заказ без user_id вставить нельзя), optional участие — foreign key допускает NULL (NULL означает, что связи нет, например у директора manager_id равен NULL). Соответствие прямое: NOT NULL на FK означает обязательное участие, nullable FK — необязательное.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Чем optionality (минимальная кардинальность) отличается от обычной кардинальности 1:N?

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

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

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

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