← Назад к вопросам
Спроектируй БД кинотеатра с залами, событиями и сиденьями
1.3 Junior🔥 141 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Спроектируй БД кинотеатра с залами, событиями и сиденьями
Это отличный вопрос о проектировании реляционной БД. Давайте спроектируем полную систему кинотеатра с поддержкой бронирования мест.
Анализ требований
Система должна поддерживать:
- Кинотеатры — физические локации
- Залы — помещения в кинотеатре с определённой конфигурацией мест
- Фильмы — контент, который показывается
- События — конкретные сеансы (фильм в определённый день и время в определённом зале)
- Сидения — места в зале с нумерацией
- Бронирования — пользователь бронирует место на событие
- Цены — разные типы мест могут иметь разные цены
SQL схема БД
-- 1. Кинотеатры
CREATE TABLE cinemas (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
city VARCHAR(100) NOT NULL,
address VARCHAR(500) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 2. Залы кинотеатра
CREATE TABLE halls (
id UUID PRIMARY KEY,
cinema_id UUID NOT NULL REFERENCES cinemas(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
capacity INT NOT NULL CHECK (capacity > 0),
rows_count INT NOT NULL CHECK (rows_count > 0),
seats_per_row INT NOT NULL CHECK (seats_per_row > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(cinema_id, name)
);
-- 3. Типы мест (эконом, премиум, VIP и т.д.)
CREATE TABLE seat_types (
id UUID PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 4. Сидения в залах
CREATE TABLE seats (
id UUID PRIMARY KEY,
hall_id UUID NOT NULL REFERENCES halls(id) ON DELETE CASCADE,
seat_type_id UUID NOT NULL REFERENCES seat_types(id) ON DELETE RESTRICT,
row_number INT NOT NULL CHECK (row_number > 0),
seat_number INT NOT NULL CHECK (seat_number > 0),
is_available BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(hall_id, row_number, seat_number),
CHECK (row_number <= (SELECT rows_count FROM halls WHERE id = hall_id)),
CHECK (seat_number <= (SELECT seats_per_row FROM halls WHERE id = hall_id))
);
-- 5. Фильмы
CREATE TABLE films (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
duration_minutes INT NOT NULL CHECK (duration_minutes > 0),
rating VARCHAR(10),
genre VARCHAR(100),
release_date DATE,
director VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 6. События (сеансы)
CREATE TABLE events (
id UUID PRIMARY KEY,
hall_id UUID NOT NULL REFERENCES halls(id) ON DELETE CASCADE,
film_id UUID NOT NULL REFERENCES films(id) ON DELETE RESTRICT,
event_start TIMESTAMPTZ NOT NULL,
event_end TIMESTAMPTZ NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'UPCOMING',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (event_start < event_end),
INDEX idx_hall_datetime (hall_id, event_start),
INDEX idx_status (status)
);
-- 7. Цены за места по типам для событий
CREATE TABLE event_seat_prices (
id UUID PRIMARY KEY,
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
seat_type_id UUID NOT NULL REFERENCES seat_types(id) ON DELETE RESTRICT,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(event_id, seat_type_id)
);
-- 8. Пользователи
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
full_name VARCHAR(255) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 9. Бронирования (резервации)
CREATE TABLE bookings (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
event_id UUID NOT NULL REFERENCES events(id) ON DELETE RESTRICT,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
total_price DECIMAL(12, 2) NOT NULL CHECK (total_price >= 0),
booking_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_event (user_id, event_id),
INDEX idx_status (status)
);
-- 10. Детали бронирования (какие места забронированы)
CREATE TABLE booking_seats (
id UUID PRIMARY KEY,
booking_id UUID NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
seat_id UUID NOT NULL REFERENCES seats(id) ON DELETE RESTRICT,
seat_type_id UUID NOT NULL REFERENCES seat_types(id) ON DELETE RESTRICT,
price_paid DECIMAL(10, 2) NOT NULL CHECK (price_paid >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(booking_id, seat_id),
UNIQUE(seat_id, (SELECT event_id FROM bookings WHERE id = booking_id))
);
-- 11. Платежи
CREATE TABLE payments (
id UUID PRIMARY KEY,
booking_id UUID NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
amount DECIMAL(12, 2) NOT NULL CHECK (amount >= 0),
payment_method VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
transaction_id VARCHAR(255),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_status (status)
);
Практические SQL запросы
-- 1. Показать доступные места для события
SELECT s.id, s.row_number, s.seat_number, st.name as seat_type, esp.price
FROM seats s
JOIN seat_types st ON s.seat_type_id = st.id
JOIN event_seat_prices esp ON st.id = esp.seat_type_id AND esp.event_id = 'EVENT_ID'
WHERE s.hall_id = (SELECT hall_id FROM events WHERE id = 'EVENT_ID')
AND s.available = true
AND s.id NOT IN (
SELECT bs.seat_id FROM booking_seats bs
JOIN bookings b ON bs.booking_id = b.id
WHERE b.event_id = 'EVENT_ID' AND b.status = 'ACTIVE'
)
ORDER BY s.row_number, s.seat_number;
-- 2. Получить события кинотеатра на неделю
SELECT e.id, f.title, h.name as hall_name, e.event_start, e.event_end,
COUNT(CASE WHEN bs.id IS NOT NULL THEN 1 END) as booked_seats,
h.capacity - COUNT(CASE WHEN bs.id IS NOT NULL THEN 1 END) as available_seats
FROM events e
JOIN films f ON e.film_id = f.id
JOIN halls h ON e.hall_id = h.id
LEFT JOIN bookings b ON e.id = b.event_id AND b.status = 'ACTIVE'
LEFT JOIN booking_seats bs ON b.id = bs.booking_id
WHERE h.cinema_id = 'CINEMA_ID'
AND e.event_start BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '7 days'
AND e.status != 'CANCELLED'
GROUP BY e.id, f.title, h.name, h.capacity, e.event_start, e.event_end
ORDER BY e.event_start;
-- 3. Занятость каждого зала по событиям
SELECT e.id as event_id, f.title, e.event_start,
COUNT(*) as total_seats,
COUNT(CASE WHEN bs.id IS NOT NULL THEN 1 END) as booked_seats,
ROUND(100.0 * COUNT(CASE WHEN bs.id IS NOT NULL THEN 1 END) / COUNT(*), 2) as occupancy_percent
FROM events e
JOIN films f ON e.film_id = f.id
JOIN seats s ON e.hall_id = s.hall_id
LEFT JOIN booking_seats bs ON s.id = bs.seat_id
LEFT JOIN bookings b ON bs.booking_id = b.id AND b.event_id = e.id AND b.status = 'ACTIVE'
WHERE e.event_start >= CURRENT_TIMESTAMP
GROUP BY e.id, f.title, e.event_start
ORDER BY e.event_start;
-- 4. Бронирования пользователя
SELECT b.id, f.title, h.name as hall_name, e.event_start,
STRING_AGG(s.row_number || '-' || s.seat_number, ', ' ORDER BY s.row_number, s.seat_number) as seats,
b.total_price, b.status
FROM bookings b
JOIN events e ON b.event_id = e.id
JOIN films f ON e.film_id = f.id
JOIN halls h ON e.hall_id = h.id
JOIN booking_seats bs ON b.id = bs.booking_id
JOIN seats s ON bs.seat_id = s.id
WHERE b.user_id = 'USER_ID'
GROUP BY b.id, f.title, h.name, e.event_start, b.total_price, b.status
ORDER BY e.event_start DESC;
Ключевые моменты дизайна
- Нормализация: 3NF
- Целостность данных: CHECK constraints, FOREIGN KEYS
- Performance: Индексы на часто используемые поля
- Масштабируемость: Partition by event_start if needed
- Auditing: created_at, updated_at на каждой таблице
- Мягкое удаление: можно добавить deleted_at для логического удаления
Diagram ERD
Cinemas (1) ---> (M) Halls
|
(1) |
├---> Seats
|
└---> Events (1) ---> (M) Bookings
| |
| ├--> BookingSeats --> Seats
| |
| └--> Payments
|
└---> Films
SeatTypes ---> Seats
---> EventSeatPrices --> Events
Users (1) ---> (M) Bookings