Как спроектировать схему базы данных для кинотеатра с залами, сеансами и местами?

«Как спроектировать схему базы данных для кинотеатра с залами, сеансами и местами?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Основные сущности и их связи:

  1. CinemaHall (Кинозал) — хранит информацию о зале.
    • id (PK), name, total_rows, seats_per_row
  2. Seat (Место) — конкретное кресло в зале.
    • id (PK), hall_id (FK → CinemaHall), row_number, seat_number, type (например, 'STANDARD', 'VIP')
  3. Movie (Фильм) — каталог фильмов.
    • id (PK), title, duration_minutes, rating, description
  4. Screening (Сеанс) — событие показа фильма в определённом зале.
    • id (PK), movie_id (FK → Movie), hall_id (FK → CinemaHall), start_time, base_price
  5. Ticket (Билет) — факт бронирования/покупки места на сеанс.
    • id (PK), screening_id (FK → Screening), seat_id (FK → Seat), status ('RESERVED', 'PURCHASED'), user_id, purchase_time

Пример DDL (PostgreSQL):

CREATE TABLE cinema_hall (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    total_rows INT NOT NULL CHECK (total_rows > 0),
    seats_per_row INT NOT NULL CHECK (seats_per_row > 0)
);

CREATE TABLE seat (
    id SERIAL PRIMARY KEY,
    hall_id INT NOT NULL REFERENCES cinema_hall(id) ON DELETE CASCADE,
    row_number INT NOT NULL,
    seat_number INT NOT NULL,
    type VARCHAR(20) DEFAULT 'STANDARD',
    UNIQUE(hall_id, row_number, seat_number)
);

CREATE TABLE movie (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    duration_minutes INT NOT NULL CHECK (duration_minutes > 0),
    rating VARCHAR(10),
    description TEXT
);

CREATE TABLE screening (
    id SERIAL PRIMARY KEY,
    movie_id INT NOT NULL REFERENCES movie(id),
    hall_id INT NOT NULL REFERENCES cinema_hall(id),
    start_time TIMESTAMP NOT NULL,
    base_price DECIMAL(10,2) NOT NULL CHECK (base_price >= 0),
    UNIQUE(hall_id, start_time) -- Предотвращает наложение сеансов в одном зале
);

CREATE TABLE ticket (
    id SERIAL PRIMARY KEY,
    screening_id INT NOT NULL REFERENCES screening(id),
    seat_id INT NOT NULL REFERENCES seat(id),
    status VARCHAR(20) NOT NULL,
    user_id INT, -- Ссылка на таблицу пользователей
    purchase_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(screening_id, seat_id) -- Гарантирует уникальность места на сеансе
);

Рекомендуемые индексы для производительности:

CREATE INDEX idx_screening_time ON screening(start_time);
CREATE INDEX idx_screening_movie ON screening(movie_id);
CREATE INDEX idx_ticket_screening ON ticket(screening_id);
CREATE INDEX idx_ticket_user ON ticket(user_id);

Почему такая схема? Она нормализована, минимизирует избыточность данных и обеспечивает целостность через внешние ключи. Уникальные ограничения предотвращают логические ошибки (два сеанса в одном зале одновременно, двойная продажа одного места).