### 语法结构 ```sql DELIMITER // CREATE TRIGGER trigger_name {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name FOR EACH ROW BEGIN -- 触发器的逻辑代码 -- 可以包含 SQL 语句、流程控制语句(如 IF、CASE 等)、变量声明等 END// DELIMITER ; ``` ### 表结构和数据 ```sql -- 创建数据库 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` 语句的基本语法 ```sql SIGNAL SQLSTATE '状态码' SET MESSAGE_TEXT = '错误消息'; ``` - **SQLSTATE**:一个 5 字符的字符串,表示错误的状态码。MySQL 使用 `'45000'` 表示用户定义的错误。 - **MESSAGE_TEXT**:自定义的错误消息,用于描述错误的原因。 ### 练习题 #### 练习题 1: 工资范围验证 创建一个触发器 `before_employee_insert`,在插入 `employee` 表数据之前,检查员工的工资是否在其对应职位的 `min_salary` 和 `max_salary` 范围内。如果超出范围,阻止插入并抛出错误。 ```sql 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`)。 ```sql 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、旧工资、新工资和变更时间。 ```sql 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` 中。 ```sql 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)。 ```sql 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` 字段。如果尝试修改,抛出错误。 ```sql 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` 中。 ```sql 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` 表数据之前,检查该部门是否还有员工。如果有员工,阻止删除并抛出错误。 ```sql 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_salary` 或 `max_salary` 字段后,自动调整 `employee` 表中相关员工的工资,使其符合新的工资范围。 ```sql 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 和变更时间。 ```sql 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` 表数据之前,检查职位名称是否已存在。如果存在,阻止插入并抛出错误。 ```sql 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` 中的员工总数。 ```sql 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` 字段。如果尝试修改,抛出错误。 ```sql 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` 表数据时,如果工资低于职位的最低工资,则自动将其调整为最低工资。 ```sql 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、姓名、离职时间和原因(假设原因由用户输入)。