CREATE TABLE TourFirm ( firm_id SERIAL PRIMARY KEY, firm_name VARCHAR(100) NOT NULL, people_count INT NOT NULL CHECK (people_count > 0) ); CREATE TABLE RoomType ( room_type_id SERIAL PRIMARY KEY, type_name VARCHAR(50) NOT NULL UNIQUE ); CREATE TABLE Room ( room_id SERIAL PRIMARY KEY, room_type_id INT NOT NULL REFERENCES RoomType(room_type_id) ON DELETE RESTRICT ON UPDATE CASCADE, room_number INT NOT NULL UNIQUE CHECK (room_number > 0), occupancy INT NOT NULL CHECK (occupancy > 0), rating DECIMAL(3,1) NOT NULL DEFAULT 0.0 CHECK (rating >= 0.0 AND rating <= 5.0) ); CREATE TABLE Client ( client_id SERIAL PRIMARY KEY, firm_id INT NOT NULL REFERENCES TourFirm(firm_id) ON DELETE RESTRICT ON UPDATE CASCADE, last_name VARCHAR(60) NOT NULL, first_name VARCHAR(60) NOT NULL, patronymic VARCHAR(60), phone_number VARCHAR(20) NOT NULL ); CREATE TABLE Booking ( booking_id SERIAL PRIMARY KEY, client_id INT NOT NULL REFERENCES Client(client_id) ON DELETE RESTRICT ON UPDATE CASCADE, room_id INT NOT NULL REFERENCES Room(room_id) ON DELETE RESTRICT ON UPDATE CASCADE, check_in_date DATE NOT NULL, check_out_date DATE NOT NULL, price_per_night DECIMAL(10,2) NOT NULL CHECK (price_per_night > 0),s CHECK (check_out_date > check_in_date) ); CREATE TABLE Service ( service_id SERIAL PRIMARY KEY, service_name VARCHAR(100) NOT NULL UNIQUE ); CREATE TABLE RoomTypeService ( room_type_id INT NOT NULL REFERENCES RoomType(room_type_id) ON DELETE RESTRICT ON UPDATE CASCADE, service_id INT NOT NULL REFERENCES Service(service_id) ON DELETE RESTRICT ON UPDATE CASCADE, PRIMARY KEY (room_type_id, service_id) ); SELECT * FROM booking --Расчетный атрибут — итоговая стоимость бронирования --total_cost = price_per_night * (check_out_date - check_in_date) CREATE OR REPLACE VIEW BookingCostView AS SELECT b.booking_id, c.last_name || ' ' || c.first_name || ' ' || COALESCE(c.patronymic, '') AS client_full_name, r.room_number, rt.type_name AS room_type, b.check_in_date, b.check_out_date, (b.check_out_date - b.check_in_date) AS nights, b.price_per_night, b.price_per_night * (b.check_out_date - b.check_in_date) AS total_cost FROM Booking b JOIN Client c ON c.client_id = b.client_id JOIN Room r ON r.room_id = b.room_id JOIN RoomType rt ON rt.room_type_id = r.room_type_id; --Проверочный запрос SELECT * FROM BookingCostView ORDER BY total_cost DESC LIMIT 10; --Возвращает итоговую стоимость конкретного бронирования --Использование в приложении: при отображении карточки брони CREATE OR REPLACE FUNCTION get_booking_cost(p_booking_id INT) RETURNS DECIMAL(10,2) LANGUAGE plpgsql AS $$ DECLARE v_cost DECIMAL(10,2); BEGIN SELECT price_per_night * (check_out_date - check_in_date) INTO v_cost FROM Booking WHERE booking_id = p_booking_id; IF NOT FOUND THEN RAISE EXCEPTION 'Бронирование с ID % не найдено', p_booking_id; END IF; RETURN v_cost; END; $$; SELECT get_booking_cost(2); -- Создаёт новое бронирование CREATE OR REPLACE PROCEDURE create_booking( p_client_id INT, p_room_id INT, p_check_in_date DATE, p_check_out_date DATE, p_price_per_night DECIMAL(10,2) ) LANGUAGE plpgsql AS $$ BEGIN IF p_check_out_date <= p_check_in_date THEN RAISE EXCEPTION 'Дата выезда должна быть позже даты заезда'; END IF; IF NOT EXISTS (SELECT 1 FROM Client WHERE client_id = p_client_id) THEN RAISE EXCEPTION 'Клиент с ID % не найден', p_client_id; END IF; IF NOT EXISTS (SELECT 1 FROM Room WHERE room_id = p_room_id) THEN RAISE EXCEPTION 'Комната с ID % не найдена', p_room_id; END IF; IF EXISTS ( SELECT 1 FROM Booking WHERE room_id = p_room_id AND check_in_date < p_check_out_date AND check_out_date > p_check_in_date ) THEN RAISE EXCEPTION 'Комната % уже забронирована на указанные даты', p_room_id; END IF; INSERT INTO Booking (client_id, room_id, check_in_date, check_out_date, price_per_night) VALUES (p_client_id, p_room_id, p_check_in_date, p_check_out_date, p_price_per_night); RAISE NOTICE 'Бронирование успешно создано для клиента ID %', p_client_id; END; $$; CALL create_booking(3, 5, '2026-03-01', '2026-03-014', 2500.00); SELECT setval('tourfirm_firm_id_seq', (SELECT MAX(firm_id) FROM TourFirm)); SELECT setval('client_client_id_seq', (SELECT MAX(client_id) FROM Client)); SELECT setval('room_room_id_seq', (SELECT MAX(room_id) FROM Room)); SELECT setval('roomtype_room_type_id_seq',(SELECT MAX(room_type_id) FROM RoomType)); SELECT setval('service_service_id_seq', (SELECT MAX(service_id) FROM Service)); SELECT setval('booking_booking_id_seq', (SELECT MAX(booking_id) FROM Booking)); --Логирование бронирований CREATE TABLE AuditLog ( log_id SERIAL PRIMARY KEY, tbl_name VARCHAR(50), action VARCHAR(10), changed_at TIMESTAMP DEFAULT NOW(), old_data JSONB, new_data JSONB ); CREATE OR REPLACE FUNCTION audit_booking() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN INSERT INTO AuditLog (tbl_name, action, old_data, new_data) VALUES ( TG_TABLE_NAME, TG_OP, CASE WHEN TG_OP = 'DELETE' THEN row_to_json(OLD)::jsonb ELSE NULL END, CASE WHEN TG_OP != 'DELETE' THEN row_to_json(NEW)::jsonb ELSE NULL END ); RETURN NEW; END; $$; CREATE TRIGGER trg_audit_booking AFTER INSERT OR UPDATE OR DELETE ON Booking FOR EACH ROW EXECUTE FUNCTION audit_booking();