class-notes/2207/触发器练习题.md
2025-03-21 09:18:10 +08:00

14 KiB
Raw Blame History

语法结构

DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
    -- 触发器的逻辑代码
    -- 可以包含 SQL 语句、流程控制语句(如 IF、CASE 等)、变量声明等
END//
DELIMITER ;

表结构和数据

-- 创建数据库
CREATE DATABASE company CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE company;

-- 创建 department 表
CREATE TABLE department (
    dept_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '部门ID主键',
    dept_name VARCHAR(100) NOT NULL COMMENT '部门名称',
    location VARCHAR(100) NOT NULL COMMENT '部门所在地'
);

-- 创建 job 表
CREATE TABLE job (
    job_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '职位ID主键',
    job_title VARCHAR(100) NOT NULL COMMENT '职位名称',
    min_salary DECIMAL(10, 2) NOT NULL COMMENT '最低工资',
    max_salary DECIMAL(10, 2) NOT NULL COMMENT '最高工资'
);

-- 创建 employee 表
CREATE TABLE employee (
    emp_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '员工ID主键',
    emp_name VARCHAR(100) NOT NULL COMMENT '员工姓名',
    salary DECIMAL(10, 2) NOT NULL COMMENT '员工工资',
    hire_date DATE NOT NULL COMMENT '入职日期',
    dept_id INT COMMENT '部门ID外键',
    job_id INT COMMENT '职位ID外键',
    FOREIGN KEY (dept_id) REFERENCES department(dept_id),
    FOREIGN KEY (job_id) REFERENCES job(job_id)
);

INSERT INTO department (dept_name, location) VALUES
('HR', 'New York'),
('IT', 'San Francisco'),
('Finance', 'Chicago'),
('Marketing', 'Los Angeles'),
('Sales', 'Boston'),
('Operations', 'Houston'),
('Research', 'Seattle'),
('Customer Support', 'Austin');

INSERT INTO job (job_title, min_salary, max_salary) VALUES
('Manager', 60000.00, 100000.00),
('Developer', 50000.00, 80000.00),
('Analyst', 45000.00, 70000.00),
('Designer', 40000.00, 65000.00),
('Sales Executive', 35000.00, 60000.00),
('HR Specialist', 40000.00, 70000.00),
('Finance Analyst', 50000.00, 75000.00),
('Marketing Coordinator', 45000.00, 70000.00),
('Operations Manager', 55000.00, 90000.00),
('Research Scientist', 60000.00, 95000.00),
('Customer Support Representative', 35000.00, 55000.00);

INSERT INTO employee (emp_name, salary, hire_date, dept_id, job_id) VALUES
('Alice', 70000.00, '2020-01-15', 1, 1),
('Bob', 65000.00, '2019-03-22', 2, 2),
('Charlie', 55000.00, '2018-07-30', 2, 3),
('David', 60000.00, '2021-05-10', 3, 4),
('Eva', 50000.00, '2017-11-05', 4, 5),
('Frank', 75000.00, '2016-09-12', 5, 1),
('Grace', 48000.00, '2022-02-20', 1, 2),
('Henry', 52000.00, '2021-08-15', 2, 3),
('Ivy', 58000.00, '2020-04-10', 3, 4),
('Jack', 62000.00, '2019-12-01', 4, 5),
('Katie', 53000.00, '2021-07-25', 5, 6),
('Leo', 67000.00, '2018-05-18', 6, 7),
('Mona', 49000.00, '2020-11-30', 7, 8),
('Nina', 71000.00, '2019-02-14', 8, 9),
('Oscar', 56000.00, '2022-03-05', 1, 10),
('Paul', 63000.00, '2021-09-12', 2, 11),
('Quincy', 54000.00, '2020-06-20', 3, 1),
('Rachel', 59000.00, '2019-08-15', 4, 2),
('Steve', 68000.00, '2018-12-10', 5, 3),
('Tina', 51000.00, '2022-01-22', 6, 4),
('Uma', 72000.00, '2021-04-18', 7, 5),
('Victor', 57000.00, '2020-07-30', 8, 6),
('Wendy', 64000.00, '2019-10-05', 1, 7),
('Xander', 50000.00, '2022-05-12', 2, 8),
('Yara', 69000.00, '2021-03-25', 3, 9),
('Zack', 55000.00, '2020-09-15', 4, 10),
('Amy', 61000.00, '2019-06-20', 5, 11),
('Brian', 74000.00, '2018-04-10', 6, 1),
('Cathy', 52000.00, '2022-07-18', 7, 2),
('Derek', 67000.00, '2021-12-01', 8, 3);

SIGNAL 语句的基本语法

SIGNAL SQLSTATE '状态码'
SET MESSAGE_TEXT = '错误消息';
  • SQLSTATE:一个 5 字符的字符串表示错误的状态码。MySQL 使用 '45000' 表示用户定义的错误。
  • MESSAGE_TEXT:自定义的错误消息,用于描述错误的原因。

练习题

练习题 1: 工资范围验证

创建一个触发器 before_employee_insert,在插入 employee 表数据之前,检查员工的工资是否在其对应职位的 min_salarymax_salary 范围内。如果超出范围,阻止插入并抛出错误。

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN

	IF NEW.salary > (
		SELECT j.max_salary
		FROM job j
		WHERE j.job_id = NEW.job_id
	) OR NEW.salary < (
		SELECT j.min_salary
		FROM job j
		WHERE j.job_id = NEW.job_id
	) THEN
		SIGNAL SQLSTATE '45000'
		SET MESSAGE_TEXT = '工资值错误,不在职务工资范围内';
	END IF;
END $$
DELIMITER ;

INSERT INTO employee
VALUES(DEFAULT, '雷昊', 0, NOW(), 1, 1);

练习题 2: 自动更新部门人数

创建一个触发器 after_employee_insert,在插入 employee 表数据之后,自动更新 department 表中的部门人数(假设 department 表新增一个字段 employee_count)。

ALTER TABLE department ADD employee_count INT UNSIGNED;

DELIMITER $$
DROP TRIGGER IF EXISTS after_employee_insert;
CREATE TRIGGER after_employee_insert
AFTER INSERT ON employee
FOR EACH ROW
BEGIN
		UPDATE department d
		SET d.employee_count = (
			SELECT COUNT(*)
			FROM employee e
			WHERE e.dept_id = d.dept_id
		);
END $$
DELIMITER ;

INSERT INTO employee
VALUES(DEFAULT, '雷昊', 60000.00, NOW(), 1, 1);

练习题 3: 工资变更日志

创建一个触发器 after_employee_update,在更新 employee 表的 salary 字段后,将变更记录插入到一个新的日志表 salary_log 中,记录员工 ID、旧工资、新工资和变更时间。

DROP TABLE IF EXISTS salary_log;
CREATE TABLE salary_log(
	emp_id INT,
	old_salary DECIMAL(10, 2),
	new_slaray DECIMAL(10, 2),
	update_date DATE
);

DELIMITER $$
DROP TRIGGER IF EXISTS after_employee_update;
CREATE TRIGGER after_employee_update
BEFORE UPDATE ON employee
FOR EACH ROW
BEGIN
		IF NEW.salary <> OLD.salary THEN
			INSERT INTO salary_log()
			VALUES(NEW.emp_id, OLD.salary, NEW.salary, NOW());
		END IF;
END $$
DELIMITER ;

UPDATE employee e
SET e.salary = 10
WHERE e.emp_id = 1;

练习题 4: 删除员工时备份数据

创建一个触发器 before_employee_delete,在删除 employee 表数据之前,将被删除的员工数据插入到一个备份表 employee_backup 中。

CREATE TABLE employee_backup AS
SELECT *
FROM employee
WHERE 0;

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_delete;
CREATE TRIGGER before_employee_delete
BEFORE DELETE ON employee
FOR EACH ROW
BEGIN
		INSERT INTO employee_backup()
		VALUES(OLD.emp_id, OLD.emp_name, OLD.salary, OLD.hire_date, OLD.dept_id, OLD.job_id);
END $$
DELIMITER ;

DELETE FROM employee
WHERE emp_id = 20;

练习题 5: 自动设置默认职位

创建一个触发器 before_employee_insert,在插入 employee 表数据时,如果未指定 job_id,则自动将其设置为 job 表中的默认职位(假设默认职位的 job_id 为 1

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
		IF NEW.job_id IS NULL THEN
			SET NEW.job_id = 1;
		END IF;
END $$
DELIMITER ;

INSERT INTO employee
VALUES(DEFAULT, '雷昊', 60000.00, NOW(), 1, NULL);

练习题 6: 防止修改入职日期

创建一个触发器 before_employee_update,在更新 employee 表数据时,防止修改 hire_date 字段。如果尝试修改,抛出错误。

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_update;
CREATE TRIGGER before_employee_update
BEFORE UPDATE ON employee
FOR EACH ROW
BEGIN
	IF NEW.hire_date <> OLD.hire_date THEN
			SIGNAL SQLSTATE '45000'
			SET MESSAGE_TEXT='不允许修改用户的入职时间';
	END IF;
END $$
DELIMITER ;
UPDATE employee e
SET e.hire_date = '2025-3-20'
WHERE e.emp_id = 1;

练习题 7: 自动计算工龄

创建一个触发器 before_employee_insert,在插入 employee 表数据时,自动计算员工的工龄(以年为单位),并将其存储到一个新字段 years_of_service 中。

ALTER TABLE employee ADD years_of_service INT;

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
	SET NEW.years_of_service = TIMESTAMPDIFF(YEAR,NEW.hire_date,NOW());
END $$
DELIMITER ;

SELECT TIMESTAMPDIFF(YEAR,e.hire_date,NOW())
FROM employee e;

INSERT INTO employee
VALUES(DEFAULT, '雷昊', 60000.00, '2022-12-31', 1, 1, NULL);

练习题 8: 防止删除部门

创建一个触发器 before_department_delete,在删除 department 表数据之前,检查该部门是否还有员工。如果有员工,阻止删除并抛出错误。

DELIMITER $$
DROP TRIGGER IF EXISTS before_department_delete;
CREATE TRIGGER before_department_delete
BEFORE DELETE ON department
FOR EACH ROW
BEGIN
	IF (SELECT COUNT(*) FROM employee e WHERE e.dept_id = OLD.dept_id) > 0 THEN
		SIGNAL SQLSTATE '45000'
		SET MESSAGE_TEXT = '该部门有员工无法删除';
	END IF;
END $$
DELIMITER ;

DELETE FROM department
WHERE dept_id = 1;

练习题 9: 自动更新职位工资范围

创建一个触发器 after_job_update,在更新 job 表的 min_salarymax_salary 字段后,自动调整 employee 表中相关员工的工资,使其符合新的工资范围。

DELIMITER $$
DROP TRIGGER IF EXISTS after_job_update;
CREATE TRIGGER after_job_update
AFTER UPDATE ON job
FOR EACH ROW
BEGIN
	UPDATE employee
	SET salary = IF(salary > NEW.max_salary, NEW.max_salary, IF(salary < NEW.min_salary, min_salary, salary))
	WHERE job_id = NEW.job_id;
END $$
DELIMITER ;

练习题 10: 记录部门变更

创建一个触发器 after_employee_update,在更新 employee 表的 dept_id 字段后,将变更记录插入到一个新的日志表 department_change_log 中,记录员工 ID、旧部门 ID、新部门 ID 和变更时间。

CREATE TABLE department_change_log(
	emp_id INT,
	old_dept_id INT,
	new_dept_id INT,
	update_date DATE
);

DELIMITER $$
DROP TRIGGER IF EXISTS after_employee_update;
CREATE TRIGGER after_employee_update
AFTER UPDATE ON employee
FOR EACH ROW
BEGIN
	IF NEW.dept_id <> OLD.dept_id THEN
		INSERT INTO department_change_log()
		VALUES(NEW.emp_id, OLD.dept_id, NEW.dept_id, NOW());
	END IF;
END $$
DELIMITER ;

练习题 11: 防止插入重复职位

创建一个触发器 before_job_insert,在插入 job 表数据之前,检查职位名称是否已存在。如果存在,阻止插入并抛出错误。

DELIMITER $$
DROP TRIGGER IF EXISTS before_job_insert;
CREATE TRIGGER before_job_insert
BEFORE INSERT ON job
FOR EACH ROW
BEGIN
	IF (SELECT COUNT(*) FROM job WHERE job_id = NEW.job_id) = 1  THEN
			SIGNAL SQLSTATE '45000'
			SET MESSAGE_TEXT = '职位名称已存在';
	END IF;
END $$
DELIMITER ;

练习题 12: 自动更新员工总数

创建一个触发器 after_employee_insert,在插入 employee 表数据之后,自动更新一个统计表 employee_statistics 中的员工总数。

CREATE TABLE employee_statistics(
	marking VARCHAR(20),
	num INT
);
INSERT INTO employee_statistics()
VALUES('count', 0);

DELIMITER $$
DROP TRIGGER IF EXISTS after_employee_insert;
CREATE TRIGGER after_employee_insert
AFTER INSERT ON employee
FOR EACH ROW
BEGIN
	UPDATE employee_statistics
	SET num = (SELECT COUNT(*) FROM employee)
	WHERE marking = 'count';
END $$
DELIMITER ;

练习题 13: 防止修改部门所在地

创建一个触发器 before_department_update,在更新 department 表数据时,防止修改 location 字段。如果尝试修改,抛出错误。

DELIMITER $$
DROP TRIGGER IF EXISTS before_department_update;
CREATE TRIGGER before_department_update
BEFORE UPDATE ON department
FOR EACH ROW
BEGIN
	IF NEW.location <> OLD.location THEN
		SIGNAL SQLSTATE '45000'
		SET MESSAGE_TEXT = '无法更新部分的位置';
	END IF;
END $$
DELIMITER ;

练习题 14: 自动调整工资

创建一个触发器 before_employee_insert,在插入 employee 表数据时,如果工资低于职位的最低工资,则自动将其调整为最低工资。

DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
	IF NEW.salary < (SELECT min_salary FROM job WHERE job_id = NEW.job_id ) THEN
		SET NEW.salary = min_salary;
	END IF;
END $$
DELIMITER ;

练习题 15: 记录职位变更

创建一个触发器 after_employee_update,在更新 employee 表的 job_id 字段后,将变更记录插入到一个新的日志表 job_change_log 中,记录员工 ID、旧职位 ID、新职位 ID 和变更时间。

练习题 16: 防止删除职位

创建一个触发器 before_job_delete,在删除 job 表数据之前,检查该职位是否还有员工。如果有员工,阻止删除并抛出错误。

练习题 17: 自动更新部门平均工资

创建一个触发器 after_employee_insert,在插入 employee 表数据之后,自动更新 department 表中的部门平均工资(假设 department 表新增一个字段 avg_salary)。

练习题 18: 防止插入未来入职日期

创建一个触发器 before_employee_insert,在插入 employee 表数据时,检查 hire_date 是否晚于当前日期。如果是,阻止插入并抛出错误。

练习题 19: 自动生成员工编号

创建一个触发器 before_employee_insert,在插入 employee 表数据时,自动生成一个唯一的员工编号(格式为 EMP-年份-序号,例如 EMP-2023-001),并将其存储到一个新字段 emp_code 中。

练习题 20: 记录员工离职

创建一个触发器 after_employee_delete,在删除 employee 表数据之后,将被删除的员工信息插入到一个离职记录表 employee_exit_log 中,记录员工 ID、姓名、离职时间和原因假设原因由用户输入