16 KiB
语法结构
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_salary
和 max_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_salary
或 max_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 和变更时间。
CREATE TABLE job_change_log(
emp_id INT,
old_job_id INT,
new_job_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.job_id <> OLD.job_id THEN
INSERT INTO job_change_log()
VALUES(NEW.emp_id, OLD.job_id, NEW.job_id, NOW());
END IF;
END $$
DELIMITER ;
练习题 16: 防止删除职位
创建一个触发器 before_job_delete
,在删除 job
表数据之前,检查该职位是否还有员工。如果有员工,阻止删除并抛出错误。
DELIMITER $$
DROP TRIGGER IF EXISTS before_job_delete;
CREATE TRIGGER before_job_delete
BEFORE DELETE ON job
FOR EACH ROW
BEGIN
IF (SELECT COUNT(*) FROM employee WHERE job_id = OLD.job_id) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '该职位还有员工';
END IF;
END $$
DELIMITER ;
练习题 17: 自动更新部门平均工资
创建一个触发器 after_employee_insert
,在插入 employee
表数据之后,自动更新 department
表中的部门平均工资(假设 department
表新增一个字段 avg_salary
)。
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.avg_salary = (SELECT AVG(e.salary) FROM employee e WHERE d.dept_id = e.dept_id)
WHERE NEW.dept_id = d.dept_id;
END $$
DELIMITER ;
练习题 18: 防止插入未来入职日期
创建一个触发器 before_employee_insert
,在插入 employee
表数据时,检查 hire_date
是否晚于当前日期。如果是,阻止插入并抛出错误。
DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
IF NEW.hire_date < CURDATE() THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '入职日期不得晚于当前日期';
END IF;
END $$
DELIMITER ;
练习题 19: 自动生成员工编号
创建一个触发器 before_employee_insert
,在插入 employee
表数据时,自动生成一个唯一的员工编号(格式为 EMP-年份-序号
,例如 EMP-2023-001
),并将其存储到一个新字段 emp_code
中。
ALTER TABLE employee ADD emp_code VARCHAR(30);
DELIMITER $$
DROP TRIGGER IF EXISTS before_employee_insert;
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
SET NEW.emp_code = CONCAT('EMP-',YEAR(NOW()),'-', (LAST_INSERT_ID()+1));
END $$
DELIMITER ;
INSERT INTO employee
VALUES(DEFAULT, '雷昊', 0, NOW(), 1, 1, NULL, NULL);
练习题 20: 记录员工离职
创建一个触发器 after_employee_delete
,在删除 employee
表数据之后,将被删除的员工信息插入到一个离职记录表 employee_exit_log
中,记录员工 ID、姓名、离职时间和原因(假设原因由用户输入)。
CREATE TABLE employee_exit_log(
emp_id INT,
emp_name VARCHAR(20),
quit_date DATE,
quit_reason VARCHAR(50)
);
DELIMITER $$
DROP TRIGGER IF EXISTS after_employee_delete;
CREATE TRIGGER after_employee_delete
AFTER DELETE ON employee
FOR EACH ROW
BEGIN
INSERT INTO employee_exit_log()
VALUES(OLD.emp_id, OLD.emp_name, NOW(), NULL);
END $$
DELIMITER ;