Основы моделирования и базы данных

Моделирование предметной области. Часть 3

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

Основные мысли

В результате изучения лекции слушатель будет способен:
1. Выделять ключевые сущности и определять типы связей на основе бизнес-правил кинотеатра.
2. Проектировать первичные ключи с применением идентифицирующих и неидентифицирующих связей.
3. Строить составные первичные ключи для объектов, чья уникальность зависит от контекста.
4. Корректно ссылаться на ассоциативные сущности, стоящие за связями «многие ко многим», при построении расписания.
5. Моделировать динамические (исторические) атрибуты с помощью временных меток начала и окончания действия.
6. Использовать дискриминаторы для представления подтипов сотрудников в модели данных.
7. Обосновывать переход от естественных составных ключей к суррогатным ключам на физическом уровне.
8. Анализировать требования к уникальности заказа и при необходимости вводить дополнительные идентификаторы.
Показывать лекцию целиком
Краткое изложение
Проектирование модели данных кинотеатра

Справочник форматов

Начинаем с создания сущности Формат (Format). Её атрибуты:
Название формата (Format Name) — строковый домен, назначается первичным ключом, так как название однозначно определяет формат;
Описание (Description) — строковый домен, содержит дополнительные сведения.

Один и тот же фильм может быть закуплен в нескольких форматах (например, IMAX, 3D, 2D), а в одном формате выходит множество фильмов. Следовательно, между сущностями «Фильм» и «Формат» возникает связь многие ко многим. На уровне логики она отображается линией, а на физическом уровне позже превратится в отдельную ассоциативную таблицу.

Залы кинотеатра

Переходим к верхней части предметной области — самому кинотеатру. В нём может быть несколько залов, поэтому создаём сущность Зал (Hall). Атрибут Название зала (Hall Name) строкового типа не обеспечит глобальной уникальности: зал «Рим» может находиться и в кинотеатре №1, и в кинотеатре №4. Поэтому первичный ключ должен включать ещё и ссылку на конкретный кинотеатр.

Строим идентифицирующую связь от сущности «Кинотеатр». Пара «Название зала + Кинотеатр» становится уникальным идентификатором: в рамках одного кинотеатра не может быть двух залов с одинаковым названием.

Теперь нужно отразить, что зал способен показывать фильмы в разных форматах. Один и тот же зал поддерживает несколько форматов, и один формат встречается во многих залах — снова связь многие ко многим, на этот раз между сущностями Зал и Формат. Соединяем их, фиксируя допустимые комбинации.

Посадочные места

Внутри каждого зала находятся посадочные места. Создаём сущность Место (Seat Place). Информации «ряд 6 место 5» недостаточно для идентификации — неизвестно, о каком зале идёт речь. Поэтому зал обязан войти в первичный ключ места. Связь от зала к месту — идентифицирующая.

Добавляем целочисленные атрибуты Ряд (Row) и Номер места (Place), оба также включаются в первичный ключ. В результате первичный ключ места оказывается составным и включает пять полей: город, кинотеатр, зал, ряд, место. Именно для упрощения работы с такими громоздкими ключами на физическом уровне позже вводят суррогатные ключи — простые числовые идентификаторы, жертвуя частью явной бизнес-логики ради производительности.

Места могут различаться по категории, например VIP или эконом. Вводим сущность Категория места (Category) с атрибутами:
Название категории (Category Name) — уникальное строковое поле, первичный ключ;
Описание (Description) — строковый домен.
Каждое место относится ровно к одной категории, тогда как одна категория охватывает много мест. Связь от категории к месту — один ко многим, неидентифицирующая, потому что место уже полностью идентифицировано своим составным ключом, а категория лишь добавляется как внешний ключ.

Расписание сеансов

Теперь нужно связать фильмы и залы через конкретные сеансы. Создаём сущность Сеанс (Session). Её собственный атрибут — Дата и время начала (Start DateTime) с точностью до минуты, включается в первичный ключ.

Сеанс обязан указывать, какой фильм в каком формате будет показан. Критичный момент: связь должна отходить не от отдельных таблиц «Фильм» и «Формат», а от ассоциативной сущности «Фильм-Формат», которая на логическом уровне скрыта за линией многие ко многим. Если провести две отдельные связи (от фильма и от формата), можно случайно создать сеанс для несуществующей пары — например, поставить фильм, закупленный только в 2D и 3D, в формате IMAX. На уровне физики появляется перекрёстная таблица, и именно от неё тянется связь к сеансу, гарантируя корректность.

Далее соединяем сеанс с сущностью Зал идентифицирующей связью. Первичный ключ сеанса формируется комбинацией времени начала, зала и пары «фильм-формат». Расписание готово.

Заказы и кассиры

Переходим к продаже билетов. Создаём сущность Заказ (Order). Заказ трактуется как факт обращения клиента к кассе. В рамках одного заказа можно купить любое количество билетов, но только на один сеанс. Если посетитель берёт билеты на разные сеансы — это разные заказы.

Атрибуты заказа:
• ссылка на Сеанс;
Дата и время заказа (Order DateTime) с точностью до секунды.

Пара «Сеанс + Order DateTime» образует первичный ключ, так как два заказа на один сеанс не могут быть оформлены в абсолютно одинаковое время (вплоть до секунды).

Однако если в кинотеатре несколько касс, два разных клиента теоретически могут совершить покупку на один сеанс одновременно. Тогда для уникальности в первичный ключ требуется добавить ещё и кассира. У нас есть сущность Персонал (Employee). Чтобы выделить именно кассиров, создаём дискриминатор (неполный подтип) — сущность Кассир (Cashier), наследующую персонал. Связь от кассира к заказу может быть идентифицирующей (если нужна уникальность через кассира) или неидентифицирующей (если совпадения исключены либо касса одна). Выбор зависит от конкретных бизнес-правил.

Динамическая цена билета

Стоимость билета не привязывается к конкретному фильму. Вместо этого она определяется четырьмя факторами:
Тип дня (Day Type) — будний, выходной, праздничный;
Время суток (Time) — утро, день, вечер;
Формат показа (Format) — 2D, 3D, IMAX и т.д.;
Категория места (Category) — VIP, эконом.

Создаём сущность Цена (Price) с первичным ключом, состоящим из перечисленных четырёх атрибутов. Однако цена меняется с течением времени, поэтому таблицу делают динамической (темпоральной). Для этого добавляют три атрибута:
Дата начала действия (Change Start) — момент, с которого цена вступает в силу;
Дата окончания действия (Change End) — момент прекращения действия;
Значение цены (Price) — денежный тип.

Первичный ключ дополняется полем Change Start. Когда цена изменяется, создаётся новая запись, у которой Change Start совпадает с Change End предыдущей. Таким образом, в любой момент времени можно точно определить действующую стоимость билета для конкретных параметров.

Детали заказа

Осталось зафиксировать, какие именно билеты куплены в рамках каждого заказа. Вводим сущность Деталь заказа (Order Detail). Её первичный ключ состоит из:
• ссылки на Заказ (идентифицирующая связь);
Номер позиции (Line Item) — целое число, обозначающее порядок билета в чеке (первый, второй, третий и т.д.).

Вторичным атрибутом по неидентифицирующей связи выступает Место (Seat Place), на которое продан данный билет. Так мы связываем каждую строку заказа с конкретным посадочным местом.

Заключительные замечания

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

Краткие итоги

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

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

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

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

В итоге выстроенная схема, будучи учебной, содержит набор универсальных шаблонов: иерархические составные ключи, перекрёстные таблицы для «многие ко многим», темпоральные атрибуты и дискриминаторы подтипов. Эти шаблоны не привязаны к домену кинотеатра и легко переносятся на задачи складского учёта, логистики или управления персоналом, формируя основу грамотного проектирования реляционных баз данных.
Проектирование начинается с создания сущности Формат (Format). Первичный ключ — Format Name, дополнительный атрибут — описание. Между фильмом и форматом связь многие ко многим, так как фильм закупается в нескольких форматах, а формат используется многими фильмами.

Далее вводится сущность Зал (Hall). Его атрибут Hall Name не уникален глобально, поэтому первичный ключ составляют название зала и ссылка на кинотеатр (идентифицирующая связь). Зал также связывается многие ко многим с форматом — так фиксируется, в каких форматах зал может показывать.

Посадочное место оформляется сущностью Место (Seat Place). Место идентифицируется только в контексте зала, поэтому связь от зала идентифицирующая. Первичный ключ дополняется атрибутами ряд и номер места, что в итоге даёт длинный составной ключ из пяти полей. Для упрощения на физическом уровне позже вводят суррогатные ID. Сущность Категория места (Category) с названием и описанием связывается с местом неидентифицирующей связью один ко многим — место имеет одну категорию, но не идентифицируется ею.

Расписание воплощается сущностью Сеанс (Session). Её собственный атрибут Start DateTime (дата и время начала с точностью до минуты) входит в первичный ключ. Ключевой момент: сеанс должен ссылаться не на отдельные «Фильм» и «Формат», а на ассоциативную сущность «Фильм-Формат» — физическую таблицу, реализующую связь многие ко многим. Это гарантирует, что сеанс назначается только для реально закупленных пар. Дополнительно сеанс идентифицирующей связью соединяется с залом. Первичный ключ сеанса образуют: время начала, зал и пара «фильм-формат».

Для продажи билетов создаётся Заказ (Order), который моделирует одно обращение к кассе и может содержать несколько билетов на один сеанс. Атрибуты: сеанс и Order DateTime (точное время заказа). Пара «сеанс + время» образует первичный ключ. Если допускаются одновременные покупки с разных касс, для уникальности в первичный ключ вводится кассир. Кассир выделяется как подтип (дискриминатор) сущности Персонал (Employee).

Цена билета зависит от типа дня, времени суток, формата и категории места, но не от конкретного фильма. Создаётся темпоральная сущность Цена (Price). Её первичный ключ включает перечисленные четыре фактора и Change Start — дату начала действия цены. Добавляются также Change End (окончание действия) и само значение цены. При изменении стоимости создаётся новая запись со своим периодом действия, причём Change Start новой записи совпадает с Change End предыдущей. Так хранится полная история цен без пересечений.

Наконец, фиксируется состав купленных билетов через Деталь заказа (Order Detail). Первичный ключ: ссылка на заказ (идентифицирующая связь) и номер позиции в чеке. Вторичным атрибутом по неидентифицирующей связи выступает конкретное место, на которое продан билет.

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

Выводы

1. Название зала уникально только в пределах одного кинотеатра, поэтому зал идентифицируется составным ключом (название + кинотеатр).
2. Посадочное место определяется с привязкой к залу, что приводит к длинному составному первичному ключу (до пяти полей).
3. Длинные естественные ключи на физическом уровне часто заменяют суррогатными числовыми идентификаторами для повышения производительности.
4. Связь многие ко многим «фильм-формат» на физическом уровне требует отдельной ассоциативной таблицы.
5. При построении сеанса ссылаться нужно именно на ассоциативную таблицу «фильм-формат», чтобы исключить несуществующие комбинации.
6. Сеанс однозначно идентифицируется временем начала, залом и парой «фильм-формат».
7. Заказ моделируется как факт обращения на один сеанс с возможностью купить несколько билетов.
8. Уникальность заказа достигается комбинацией сеанса и точного времени оформления, а при необходимости добавляется кассир.
9. Для выделения ролей сотрудников используется дискриминатор (подтип «Кассир» от «Персонала»).
10. Цена билета зависит от типа дня, времени суток, формата показа и категории места, а не от конкретного фильма.
11. Для учёта изменения цены во времени применяется темпоральная таблица с датами начала и окончания действия.
12. Дата начала действия цены включается в первичный ключ, чтобы исключить пересечение периодов действия разных цен.

Вопросы для самопроверки

1. Почему одного названия зала недостаточно для его уникальной идентификации?
2. Какую роль играет идентифицирующая связь между залом и посадочным местом?
3. Чем идентифицирующая связь отличается от неидентифицирующей на уровне первичных ключей?
4. Почему на логическом уровне связь многие ко многим не превращается в отдельную таблицу, а на физическом — превращается?
5. Какая ошибка может возникнуть, если при создании сеанса связать его напрямую с сущностями «Фильм» и «Формат» по отдельности?
6. Из каких компонентов складывается первичный ключ сеанса в построенной модели?
7. Как выбранная семантика заказа (транзакция на один сеанс) влияет на его первичный ключ?
8. В каком случае кассир может потребоваться в первичном ключе заказа, а когда достаточно неидентифицирующей связи?
9. Для чего нужен дискриминатор при создании сущности «Кассир»?
10. Какие четыре фактора определяют цену билета и почему фильм не включён в их число?
11. Как с помощью полей Change Start и Change End обеспечить непротиворечивую историю изменения цен?
12. Каким образом деталь заказа связывает конкретный билет с посадочным местом?
Вернуться к учебному плану