480 lines
14 KiB
Markdown
480 lines
14 KiB
Markdown
### 语法结构
|
||
|
||
```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` 表数据时,如果工资低于职位的最低工资,则自动将其调整为最低工资。
|
||
|
||
#### 练习题 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、姓名、离职时间和原因(假设原因由用户输入)。 |