Загрузка данных
-- ============================================================
-- БД: IT-куб — автоматизация приёма и распределения заявок
-- СУБД: MySQL 8.0+
-- Нормализация: 3НФ
-- ============================================================
DROP DATABASE IF EXISTS it_cube;
CREATE DATABASE it_cube
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE it_cube;
-- ============================================================
-- 1. ТАБЛИЦА: родители
-- ============================================================
CREATE TABLE родители (
код_родителя INT AUTO_INCREMENT PRIMARY KEY,
фамилия VARCHAR(50) NOT NULL,
имя VARCHAR(50) NOT NULL,
отчество VARCHAR(50) NULL,
телефон VARCHAR(20) NOT NULL UNIQUE,
электронная_почта VARCHAR(100) NOT NULL UNIQUE,
снилс CHAR(11) NULL UNIQUE,
дата_регистрации DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- ============================================================
-- 2. ТАБЛИЦА: дети
-- ============================================================
CREATE TABLE дети (
код_ребёнка INT AUTO_INCREMENT PRIMARY KEY,
фамилия VARCHAR(50) NOT NULL,
имя VARCHAR(50) NOT NULL,
отчество VARCHAR(50) NULL,
дата_рождения DATE NOT NULL,
снилс CHAR(11) NULL UNIQUE,
код_родителя INT NOT NULL,
CONSTRAINT fk_дети_родители
FOREIGN KEY (код_родителя)
REFERENCES родители(код_родителя)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT chk_дата_рождения
CHECK (дата_рождения <= CURDATE())
) ENGINE=InnoDB;
-- ============================================================
-- 3. ТАБЛИЦА: преподаватели
-- ============================================================
CREATE TABLE преподаватели (
код_преподавателя INT AUTO_INCREMENT PRIMARY KEY,
фамилия VARCHAR(50) NOT NULL,
имя VARCHAR(50) NOT NULL,
отчество VARCHAR(50) NULL,
специализация VARCHAR(100) NOT NULL,
телефон VARCHAR(20) NOT NULL UNIQUE,
электронная_почта VARCHAR(100) NOT NULL UNIQUE
) ENGINE=InnoDB;
-- ============================================================
-- 4. ТАБЛИЦА: программы
-- ============================================================
CREATE TABLE программы (
код_программы INT AUTO_INCREMENT PRIMARY KEY,
название VARCHAR(100) NOT NULL UNIQUE,
описание TEXT NULL,
возраст_мин TINYINT UNSIGNED NOT NULL,
возраст_макс TINYINT UNSIGNED NOT NULL,
длительность_часов SMALLINT UNSIGNED NOT NULL,
CONSTRAINT chk_возраст
CHECK (возраст_мин < возраст_макс AND возраст_мин >= 5)
) ENGINE=InnoDB;
-- ============================================================
-- 5. ТАБЛИЦА: кружки
-- ============================================================
CREATE TABLE кружки (
код_кружка INT AUTO_INCREMENT PRIMARY KEY,
код_программы INT NOT NULL,
код_преподавателя INT NOT NULL,
аудитория VARCHAR(20) NULL,
учебный_год VARCHAR(9) NOT NULL,
макс_мест SMALLINT UNSIGNED NOT NULL,
свободных_мест SMALLINT UNSIGNED NOT NULL,
CONSTRAINT fk_кружки_программы
FOREIGN KEY (код_программы)
REFERENCES программы(код_программы)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT fk_кружки_преподаватели
FOREIGN KEY (код_преподавателя)
REFERENCES преподаватели(код_преподавателя)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT chk_мест
CHECK (свободных_мест <= макс_мест AND свободных_мест >= 0)
) ENGINE=InnoDB;
-- ============================================================
-- 6. ТАБЛИЦА: расписание
-- ============================================================
CREATE TABLE расписание (
код_занятия INT AUTO_INCREMENT PRIMARY KEY,
код_кружка INT NOT NULL,
день_недели ENUM('Пн','Вт','Ср','Чт','Пт','Сб','Вс') NOT NULL,
время_начала TIME NOT NULL,
время_окончания TIME NOT NULL,
CONSTRAINT fk_расписание_кружки
FOREIGN KEY (код_кружка)
REFERENCES кружки(код_кружка)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT chk_время
CHECK (время_начала < время_окончания)
) ENGINE=InnoDB;
-- ============================================================
-- 7. ТАБЛИЦА: заявки
-- ============================================================
CREATE TABLE заявки (
код_заявки INT AUTO_INCREMENT PRIMARY KEY,
код_ребёнка INT NOT NULL,
код_кружка INT NULL,
статус VARCHAR(20) NOT NULL DEFAULT 'новая',
дата_подачи DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
дата_изменения DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
комментарий TEXT NULL,
CONSTRAINT fk_заявки_дети
FOREIGN KEY (код_ребёнка)
REFERENCES дети(код_ребёнка)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk_заявки_кружки
FOREIGN KEY (код_кружка)
REFERENCES кружки(код_кружка)
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT chk_статус
CHECK (статус IN ('новая','на_проверке','одобрена',
'зачислен','отклонена','отменена')),
CONSTRAINT chk_зачислен_с_кружком
CHECK (
(статус = 'зачислен' AND код_кружка IS NOT NULL)
OR
(статус <> 'зачислен')
)
) ENGINE=InnoDB;
-- ============================================================
-- ИНДЕКСЫ
-- ============================================================
CREATE INDEX idx_дети_родитель ON дети(код_родителя);
CREATE INDEX idx_заявки_ребёнок ON заявки(код_ребёнка);
CREATE INDEX idx_заявки_кружок ON заявки(код_кружка);
CREATE INDEX idx_заявки_статус ON заявки(статус);
CREATE INDEX idx_кружки_программа ON кружки(код_программы);
CREATE INDEX idx_кружки_преподаватель ON кружки(код_преподавателя);
CREATE INDEX idx_расписание_кружок ON расписание(код_кружка);
-- ============================================================
-- ТРИГГЕРЫ УЧЁТА СВОБОДНЫХ МЕСТ
-- ============================================================
DELIMITER $$
-- Проверка перед вставкой заявки
CREATE TRIGGER trg_заявки_before_insert
BEFORE INSERT ON заявки
FOR EACH ROW
BEGIN
IF NEW.статус = 'зачислен' THEN
IF NEW.код_кружка IS NULL THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нельзя зачислить без указания кружка';
END IF;
IF (SELECT свободных_мест FROM кружки
WHERE код_кружка = NEW.код_кружка) <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нет свободных мест в выбранном кружке';
END IF;
END IF;
END$$
-- Уменьшение мест после вставки зачисленной заявки
CREATE TRIGGER trg_заявки_after_insert
AFTER INSERT ON заявки
FOR EACH ROW
BEGIN
IF NEW.статус = 'зачислен' AND NEW.код_кружка IS NOT NULL THEN
UPDATE кружки
SET свободных_мест = свободных_мест - 1
WHERE код_кружка = NEW.код_кружка;
END IF;
END$$
-- Проверка перед обновлением заявки
CREATE TRIGGER trg_заявки_before_update
BEFORE UPDATE ON заявки
FOR EACH ROW
BEGIN
IF NEW.статус = 'зачислен' AND OLD.статус <> 'зачислен' THEN
IF NEW.код_кружка IS NULL THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нельзя зачислить без указания кружка';
END IF;
IF (SELECT свободных_мест FROM кружки
WHERE код_кружка = NEW.код_кружка) <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нет свободных мест в выбранном кружке';
END IF;
END IF;
IF OLD.статус = 'зачислен'
AND NEW.статус = 'зачислен'
AND NEW.код_кружка <> OLD.код_кружка THEN
IF (SELECT свободных_мест FROM кружки
WHERE код_кружка = NEW.код_кружка) <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нет свободных мест в новом кружке';
END IF;
END IF;
END$$
-- Корректировка мест после обновления заявки
CREATE TRIGGER trg_заявки_after_update
AFTER UPDATE ON заявки
FOR EACH ROW
BEGIN
IF OLD.статус = 'зачислен'
AND NEW.статус <> 'зачислен'
AND OLD.код_кружка IS NOT NULL THEN
UPDATE кружки
SET свободных_мест = свободных_мест + 1
WHERE код_кружка = OLD.код_кружка;
END IF;
IF OLD.статус <> 'зачислен'
AND NEW.статус = 'зачислен'
AND NEW.код_кружка IS NOT NULL THEN
UPDATE кружки
SET свободных_мест = свободных_мест - 1
WHERE код_кружка = NEW.код_кружка;
END IF;
IF OLD.статус = 'зачислен'
AND NEW.статус = 'зачислен'
AND OLD.код_кружка IS NOT NULL
AND NEW.код_кружка IS NOT NULL
AND OLD.код_кружка <> NEW.код_кружка THEN
UPDATE кружки
SET свободных_мест = свободных_мест + 1
WHERE код_кружка = OLD.код_кружка;
UPDATE кружки
SET свободных_мест = свободных_мест - 1
WHERE код_кружка = NEW.код_кружка;
END IF;
END$$
-- Освобождение места при удалении заявки
CREATE TRIGGER trg_заявки_after_delete
AFTER DELETE ON заявки
FOR EACH ROW
BEGIN
IF OLD.статус = 'зачислен' AND OLD.код_кружка IS NOT NULL THEN
UPDATE кружки
SET свободных_мест = свободных_мест + 1
WHERE код_кружка = OLD.код_кружка;
END IF;
END$$
-- Защита от удаления кружка с зачисленными детьми
CREATE TRIGGER trg_кружки_before_delete
BEFORE DELETE ON кружки
FOR EACH ROW
BEGIN
IF EXISTS (SELECT 1 FROM заявки
WHERE код_кружка = OLD.код_кружка
AND статус = 'зачислен') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Нельзя удалить кружок: в нём есть зачисленные дети';
END IF;
END$$
-- Процедура отмены заявки
CREATE PROCEDURE отменить_заявку(IN p_код_заявки INT)
BEGIN
DECLARE v_статус VARCHAR(20);
SELECT статус INTO v_статус
FROM заявки
WHERE код_заявки = p_код_заявки;
IF v_статус IS NULL THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Заявка не найдена';
END IF;
IF v_статус = 'отменена' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Заявка уже отменена';
END IF;
UPDATE заявки
SET статус = 'отменена',
код_кружка = NULL
WHERE код_заявки = p_код_заявки;
END$$
-- Процедура физического удаления заявки
CREATE PROCEDURE удалить_заявку(IN p_код_заявки INT)
BEGIN
DELETE FROM заявки WHERE код_заявки = p_код_заявки;
END$$
DELIMITER ;
-- ============================================================
-- КОНЕЦ СКРИПТА
-- ============================================================