数据库系统原理 · SQL 数据定义、更新及数据库编程 · 自学总结

本章核心:SQL 不只是查询,还包括定义数据结构、修改数据、以及用程序化的方式操作数据库。


一、SQL 数据定义语言(DDL)

1.1 是什么?

DDL(Data Definition Language)= 定义和修改数据库对象的语言,负责"建骨架"。

下辖知识点

语句 作用
CREATE 创建数据库对象(数据库、表、视图、索引、约束等)
ALTER 修改已有对象的结构
DROP 删除对象
TRUNCATE 清空表数据(保留结构)
数据类型 定义列的存储类型(INT、VARCHAR、DATE、DECIMAL 等)
完整性约束 PRIMARY KEY、FOREIGN KEY、UNIQUE、NOT NULL、CHECK、DEFAULT
索引 INDEX,加速查询的数据结构
域(Domain) 自定义数据类型(部分 DBMS 支持)
模式(Schema) 数据库对象的命名空间/容器

1.2 为什么要有 DDL?

没有 DDL,数据库就是一团混沌:

没有 DDL 的问题 DDL 解决后
不知道数据存在哪、什么格式 用 CREATE TABLE 明确定义结构
程序代码和数据结构耦合 改表结构不用改程序(数据独立性)
数据没有约束,想填什么填什么 用 CHECK、NOT NULL 等保证质量
查询慢得离谱 用 CREATE INDEX 建立索引加速
多人协作各自为政 用 Schema 做命名空间隔离

核心价值

  1. 结构化管理 ------ 数据不是乱堆的,而是有明确定义的格式。

  2. 约束前置 ------ 在数据入库前就拦住脏数据。

  3. 性能基础 ------ 索引、分区等性能优化手段都靠 DDL 实现。


1.3 怎么用?

创建表(CREATE TABLE)

复制代码
 CREATE TABLE 学生 (
     学号   CHAR(10) PRIMARY KEY,           -- 主码约束
     姓名   VARCHAR(20) NOT NULL,            -- 非空约束
     性别   CHAR(2) CHECK (性别 IN ('男','女')),  -- CHECK约束
     年龄   INT DEFAULT 18,                  -- 默认值
     系号   CHAR(10),
     身份证号 CHAR(18) UNIQUE,                -- 唯一约束
     
     -- 外码约束(表级定义)
     CONSTRAINT FK_系号 FOREIGN KEY (系号) 
         REFERENCES 系(系号)
         ON DELETE SET NULL    -- 系被删,学生系号变NULL
         ON UPDATE CASCADE     -- 系号改了,学生跟着改
 );

修改表(ALTER TABLE)

复制代码
 -- 加列
 ALTER TABLE 学生 ADD 邮箱 VARCHAR(50);
 ​
 -- 删列
 ALTER TABLE 学生 DROP COLUMN 邮箱;
 ​
 -- 改列类型
 ALTER TABLE 学生 ALTER COLUMN 年龄 SMALLINT;
 ​
 -- 加约束
 ALTER TABLE 学生 ADD CONSTRAINT CHK_年龄 CHECK (年龄 BETWEEN 15 AND 50);
 ​
 -- 删约束
 ALTER TABLE 学生 DROP CONSTRAINT CHK_年龄;

删除表(DROP TABLE)

复制代码
 DROP TABLE 学生;           -- 表结构和数据全删
 DROP TABLE 学生 CASCADE;   -- 级联删除关联对象(如外键引用的视图)

索引(CREATE INDEX)

复制代码
 -- 单列索引
 CREATE INDEX IX_姓名 ON 学生(姓名);
 ​
 -- 复合索引
 CREATE INDEX IX_系名_年龄 ON 学生(系号, 年龄);
 ​
 -- 唯一索引
 CREATE UNIQUE INDEX IX_身份证 ON 学生(身份证号);
 ​
 -- 删除索引
 DROP INDEX IX_姓名;

索引的作用:加速 WHERE、ORDER BY、JOIN 的查询速度;代价是占用空间、减慢 INSERT/UPDATE/DELETE。

模式(Schema)

复制代码
 -- 创建模式(命名空间)
 CREATE SCHEMA 教学管理;
 ​
 -- 在指定模式下建表
 CREATE TABLE 教学管理.学生 (...);
 ​
 -- 授权
 GRANT CREATE SCHEMA TO 用户名;

二、SQL 数据更新语言(DML)

2.1 是什么?

DML(Data Manipulation Language)= 操作表中数据的语言,负责"填内容"。

下辖知识点

语句 作用
INSERT 插入新数据
UPDATE 修改已有数据
DELETE 删除数据
MERGE / UPSERT 插入或更新(存在则更新,不存在则插入)
批量插入 一次插入多行
子查询插入 从查询结果插入

2.2 为什么要有 DML?

数据不是静态的,需要持续维护:

场景 DML 动作
新生入学 INSERT 插入学生记录
学生转系 UPDATE 修改系号
学生退学 DELETE 删除记录
成绩录入 INSERT / UPDATE 成绩
数据迁移 INSERT + 子查询从旧表导入

核心价值

  1. 数据生命周期管理 ------ 数据会增删改,DML 是日常运维的主力。

  2. 集合操作 ------ SQL 的 DML 操作的是集合(多行),不是逐行处理,效率高。

  3. 与查询结合 ------ INSERT/UPDATE/DELETE 可以嵌套子查询,实现复杂的数据维护。


2.3 怎么用?

插入(INSERT)

复制代码
 -- 插入单行
 INSERT INTO 学生 (学号, 姓名, 性别, 年龄, 系号)
 VALUES ('2024001', '张三', '男', 20, 'CS');
 ​
 -- 插入多行
 INSERT INTO 学生 (学号, 姓名, 性别, 年龄, 系号)
 VALUES 
     ('2024002', '李四', '女', 19, 'CS'),
     ('2024003', '王五', '男', 21, 'EE');
 ​
 -- 从查询结果插入(批量导入)
 INSERT INTO 优秀学生 (学号, 姓名, 平均分)
 SELECT 学号, 姓名, AVG(成绩)
 FROM 学生 JOIN 选课 ON 学生.学号 = 选课.学号
 GROUP BY 学号, 姓名
 HAVING AVG(成绩) > 85;

更新(UPDATE)

复制代码
 -- 单表更新
 UPDATE 学生
 SET 年龄 = 年龄 + 1
 WHERE 系号 = 'CS';
 ​
 -- 带子查询的更新
 UPDATE 学生
 SET 系号 = 'AI'
 WHERE 学号 IN (
     SELECT 学号 FROM 选课 
     WHERE 课程号 = 'AI101' 
     GROUP BY 学号 
     HAVING COUNT(*) >= 3
 );
 ​
 -- 多表关联更新(SQL Server语法)
 UPDATE 学生
 SET 学生.系号 = 新系表.新系号
 FROM 学生 JOIN 新系表 ON 学生.系号 = 新系表.旧系号;

删除(DELETE)

复制代码
 -- 条件删除
 DELETE FROM 学生 WHERE 系号 = 'CS';
 ​
 -- 带子查询的删除
 DELETE FROM 学生
 WHERE 学号 NOT IN (SELECT DISTINCT 学号 FROM 选课);
 -- 删除没选任何课的学生
 ​
 -- 清空表(可回滚)
 DELETE FROM 学生;

DELETE vs TRUNCATE 区别:

特性 DELETE TRUNCATE
语句类型 DML DDL
是否记录日志 逐行记录,日志量大 记录页级操作,日志量小
能否回滚 ✅ 可以 ✅ 可以(事务内)
能否触发触发器 ✅ 触发 DELETE 触发器 ❌ 不触发
效率
重置自增ID ❌ 不重置 ✅ 重置

三、视图(View)

3.1 是什么?

视图 = 从一个或多个基本表(或其他视图)导出的虚拟表,不存储实际数据,只保存查询定义。

下辖知识点

知识点 是什么
简单视图 基于单表、无函数、无 GROUP BY 的视图
复杂视图 多表连接、聚合函数、分组等
可更新视图 可以通过视图 INSERT/UPDATE/DELETE 基本表
不可更新视图 涉及聚合、DISTINCT、GROUP BY 等的视图,无法直接更新
WITH CHECK OPTION 限制通过视图修改的数据必须满足视图的 WHERE 条件
物化视图 实际存储查询结果的视图(有冗余,但查询快)
级联删除/更新 视图定义中的级联操作

3.2 为什么要有视图?

直接操作基本表的困境:

问题 视图解决
表结构复杂,用户只需要看几列 视图只暴露需要的列,隐藏敏感/无关字段
同样的复杂查询要写无数次 视图把查询"封装"起来,用时像查表一样简单
不同用户应该看到不同数据 视图加权限控制,实现行级/列级安全
重构表结构后,应用程序要改 视图作为抽象层,底下表结构变了,视图不变
报表查询要实时计算聚合 物化视图预存结果,查询直接读

核心价值

  1. 简化查询 ------ 复杂查询包成视图,用的时候 SELECT * FROM 视图名

  2. 安全隔离 ------ 用户只能看到视图允许看到的列和行。

  3. 逻辑独立性 ------ 表结构调整时,通过视图兼容旧接口。

  4. 性能优化(物化视图) ------ 预计算 + 预存储,报表秒出。


3.3 怎么用?

创建视图(CREATE VIEW)

复制代码
-- 简单视图:只暴露部分列
CREATE VIEW 学生基本信息 AS
SELECT 学号, 姓名, 性别, 系号
FROM 学生;

-- 复杂视图:多表连接 + 聚合
CREATE VIEW 系成绩统计 AS
SELECT 
    学生.系号,
    COUNT(DISTINCT 学生.学号) AS 学生人数,
    AVG(选课.成绩) AS 平均成绩,
    MAX(选课.成绩) AS 最高分
FROM 学生 LEFT JOIN 选课 ON 学生.学号 = 选课.学号
GROUP BY 学生.系号;

-- 安全视图:只显示成绩及格的学生
CREATE VIEW 及格学生 AS
SELECT 学号, 姓名, 系号
FROM 学生
WHERE 学号 IN (SELECT 学号 FROM 选课 WHERE 成绩 >= 60)
WITH CHECK OPTION;   -- 通过此视图插入的学生必须满足条件

使用视图

复制代码
-- 像查表一样查视图
SELECT * FROM 系成绩统计 WHERE 平均成绩 > 80;

-- 更新视图(仅限可更新视图)
UPDATE 学生基本信息 SET 系号 = 'AI' WHERE 学号 = '2024001';
-- 实际修改的是底层"学生"表

删除视图

复制代码
DROP VIEW 系成绩统计;
-- 只删视图定义,不影响基本表

物化视图(Materialized View)

复制代码
-- Oracle / PostgreSQL 语法
CREATE MATERIALIZED VIEW 月销售统计 AS
SELECT 月份, SUM(金额) AS 总额
FROM 订单
GROUP BY 月份;

-- 手动刷新
REFRESH MATERIALIZED VIEW 月销售统计;

-- 或定时自动刷新

视图 vs 物化视图:

特性 普通视图 物化视图
是否存数据 ❌ 不存,每次实时查 ✅ 存了查询结果
查询速度 慢(要执行底层查询) 快(直接读结果)
数据实时性 实时 取决于刷新策略
占用空间 几乎不占用 占用存储

四、T-SQL 简介

4.1 是什么?

T-SQL(Transact-SQL)= SQL Server 的 SQL 方言扩展,是标准 SQL 加上微软扩展的过程化编程能力。

下辖知识点

知识点 是什么
变量声明 DECLARE @变量名 类型
赋值 SET @变量 = 值SELECT @变量 = 列 FROM ...
流程控制 IF...ELSEWHILECASEGOTORETURN
批处理 GO 分隔批处理
注释 -- 单行/* 多行 */
系统函数 GETDATE()LEN()CONVERT()
TRY...CATCH 异常处理
事务控制 BEGIN TRANCOMMITROLLBACKSAVEPOINT

4.2 为什么要有 T-SQL?

标准 SQL 是声明式的,只描述要什么,不能描述"怎么做"的流程:

标准 SQL 的局限 T-SQL 解决
没法写变量暂存中间结果 @变量 存储
没法按条件分支执行 IF...ELSE
没法循环处理 WHILE
出错只能返回错误码 TRY...CATCH 优雅处理
多条语句无法打包执行 BEGIN...ENDGO
无法调用复杂业务逻辑 用存储过程/函数封装

核心价值

  1. 过程化编程 ------ 在数据库内部完成复杂逻辑,不用来回传数据到应用层。

  2. 减少网络往返 ------ 逻辑在数据库里执行,省去应用层和数据库的通信开销。

  3. 统一维护 ------ 业务逻辑写在数据库里,一处改到处生效。


4.3 怎么用?

变量与赋值

复制代码
DECLARE @系号 CHAR(10);
DECLARE @人数 INT;

-- 直接赋值
SET @系号 = 'CS';

-- 从查询结果赋值
SELECT @人数 = COUNT(*) FROM 学生 WHERE 系号 = @系号;

PRINT '该系有 ' + CAST(@人数 AS VARCHAR) + ' 人';

流程控制

复制代码
-- IF...ELSE
DECLARE @平均分 DECIMAL(5,2);
SELECT @平均分 = AVG(成绩) FROM 选课 WHERE 课程号 = 'DB';

IF @平均分 >= 80
    PRINT '成绩优秀';
ELSE IF @平均分 >= 60
    PRINT '成绩合格';
ELSE
    PRINT '需要加强';

-- WHILE 循环
DECLARE @i INT = 1;
WHILE @i <= 10
BEGIN
    INSERT INTO 测试表 (序号) VALUES (@i);
    SET @i = @i + 1;
END;

-- CASE 表达式
SELECT 姓名,
    CASE 
        WHEN 成绩 >= 90 THEN '优秀'
        WHEN 成绩 >= 80 THEN '良好'
        WHEN 成绩 >= 60 THEN '及格'
        ELSE '不及格'
    END AS 等级
FROM 学生 JOIN 选课 ON ...;

事务控制

复制代码
BEGIN TRANSACTION;

BEGIN TRY
    UPDATE 账户 SET 余额 = 余额 - 1000 WHERE 账户号 = 'A';
    UPDATE 账户 SET 余额 = 余额 + 1000 WHERE 账户号 = 'B';
    
    COMMIT;   -- 成功提交
    PRINT '转账成功';
END TRY
BEGIN CATCH
    ROLLBACK;  -- 出错回滚
    PRINT '转账失败:' + ERROR_MESSAGE();
END CATCH;

常用系统函数

复制代码
SELECT 
    GETDATE() AS 当前时间,           -- 2025-01-15 10:30:00
    LEN('数据库') AS 字符串长度,      -- 3
    CAST(123 AS VARCHAR) AS 转字符串, -- '123'
    CONVERT(VARCHAR, GETDATE(), 120) AS 格式化日期  -- 120 = ODBC 规范

五、游标(Cursor)

5.1 是什么?

游标 = 一种逐行处理查询结果集的机制,把集合型的 SQL 结果转换成一行一行处理的方式。

下辖知识点

知识点 是什么
声明游标 DECLARE 游标名 CURSOR FOR SELECT...
打开游标 OPEN
取数据 FETCH NEXT INTO @变量
循环遍历 WHILE @@FETCH_STATUS = 0
关闭游标 CLOSE
释放游标 DEALLOCATE
游标类型 只进游标、动态游标、键集驱动游标、静态游标
游标属性 @@FETCH_STATUS@@CURSOR_ROWS

5.2 为什么要有游标?

SQL 是集合操作的,但有些场景必须逐行处理:

场景 为什么需要游标
对每行数据做不同处理 比如根据成绩分段发送不同的通知
调用逐行处理的存储过程 每行都要触发一个复杂操作
结果集太大,内存放不下 用游标一次读一行,流式处理
与不支持集合操作的外部系统交互 逐行输出给旧系统
行与行之间有依赖关系 下一行的计算依赖上一行的结果

核心价值

  1. 逐行控制能力 ------ 弥补 SQL 集合操作的不足。

  2. 内存友好 ------ 不用一次性加载整个结果集。

但注意 :游标是最后手段,能用集合操作解决的优先用集合操作,因为游标性能差。


5.3 怎么用?

游标的基本使用流程

复制代码
-- 1. 声明游标
DECLARE cur_student CURSOR FOR
SELECT 学号, 姓名, 年龄 FROM 学生 WHERE 系号 = 'CS';

-- 2. 打开游标
OPEN cur_student;

-- 3. 取第一行
FETCH NEXT FROM cur_student INTO @学号, @姓名, @年龄;

-- 4. 循环处理
WHILE @@FETCH_STATUS = 0   -- 0 表示成功取到数据
BEGIN
    -- 对当前行做处理
    PRINT @姓名 + ' 的年龄是 ' + CAST(@年龄 AS VARCHAR);
    
    -- 取下一行
    FETCH NEXT FROM cur_student INTO @学号, @姓名, @年龄;
END;

-- 5. 关闭并释放
CLOSE cur_student;
DEALLOCATE cur_student;

一个完整的业务例子

复制代码
-- 给每个学生发送通知(假设有发邮件的存储过程)
DECLARE @学号 CHAR(10), @姓名 VARCHAR(20), @成绩 INT;

DECLARE cur_grade CURSOR FOR
SELECT 学生.学号, 学生.姓名, 选课.成绩
FROM 学生 JOIN 选课 ON 学生.学号 = 选课.学号
WHERE 选课.课程号 = 'DB';

OPEN cur_grade;
FETCH NEXT FROM cur_grade INTO @学号, @姓名, @成绩;

WHILE @@FETCH_STATUS = 0
BEGIN
    IF @成绩 >= 90
        EXEC 发送通知 @学号, '恭喜' + @姓名 + ',你的数据库成绩优秀!';
    ELSE IF @成绩 < 60
        EXEC 发送通知 @学号, @姓名 + ',你的数据库课程需要补考。';
    
    FETCH NEXT FROM cur_grade INTO @学号, @姓名, @成绩;
END;

CLOSE cur_grade;
DEALLOCATE cur_grade;

集合操作的替代方案(更推荐):

复制代码
-- 用 CASE 一次性处理,性能更好
SELECT 学号, 姓名,
    CASE 
        WHEN 成绩 >= 90 THEN '优秀'
        WHEN 成绩 < 60 THEN '需补考'
        ELSE '正常'
    END AS 评价
FROM 学生 JOIN 选课 ON ...;

六、存储过程(Stored Procedure)

6.1 是什么?

存储过程 = 预编译并存储在数据库中的一组 SQL 语句,可以接收参数、执行逻辑、返回结果。

下辖知识点

知识点 是什么
创建存储过程 CREATE PROCEDURE
参数 输入参数(IN)、输出参数(OUT)、输入输出参数(INOUT)
返回值 RETURN 返回整数状态码
结果集 SELECT 返回结果集
修改/删除 ALTER PROCEDUREDROP PROCEDURE
执行 EXEC / EXECUTE
递归调用 存储过程调用自身
加密 WITH ENCRYPTION 保护源码
重新编译 WITH RECOMPILE 每次执行重新生成执行计划

6.2 为什么要有存储过程?

把 SQL 语句写在应用程序里的困境:

问题 存储过程解决
同样的 SQL 在多处重复写 封装一次,到处调用
SQL 语句通过网络传来传去 只传过程名和参数,减少网络流量
数据库结构改了,所有应用代码要改 改存储过程即可,应用层无感知
复杂逻辑要在应用层和数据库间往返 逻辑在数据库内部完成,减少往返
SQL 注入攻击风险 参数化存储过程天然防注入
权限控制粒度粗 用户只需有执行存储过程的权限,无需直接访问表

核心价值

  1. 代码复用 ------ 一处写,到处用。

  2. 性能提升 ------ 预编译、减少网络传输。

  3. 安全增强 ------ 权限隔离、防 SQL 注入。

  4. 维护方便 ------ 改存储过程 = 改全局逻辑。


6.3 怎么用?

创建存储过程

复制代码
-- 简单存储过程:查询某系学生
CREATE PROCEDURE GetStudentsByDept
    @系号 VARCHAR(10)
AS
BEGIN
    SELECT 学号, 姓名, 年龄 
    FROM 学生 
    WHERE 系号 = @系号;
END;

-- 带输出参数的存储过程:统计某系人数
CREATE PROCEDURE CountStudentsByDept
    @系号 VARCHAR(10),
    @人数 INT OUTPUT
AS
BEGIN
    SELECT @人数 = COUNT(*) FROM 学生 WHERE 系号 = @系号;
    RETURN 0;  -- 返回状态码 0 表示成功
END;

-- 复杂存储过程:转账
CREATE PROCEDURE TransferMoney
    @转出账户 CHAR(10),
    @转入账户 CHAR(10),
    @金额 DECIMAL(18,2)
AS
BEGIN
    BEGIN TRANSACTION;
    BEGIN TRY
        -- 检查余额
        DECLARE @余额 DECIMAL(18,2);
        SELECT @余额 = 余额 FROM 账户 WHERE 账户号 = @转出账户;
        
        IF @余额 < @金额
        BEGIN
            ROLLBACK;
            RETURN 1;  -- 余额不足
        END;
        
        -- 执行转账
        UPDATE 账户 SET 余额 = 余额 - @金额 WHERE 账户号 = @转出账户;
        UPDATE 账户 SET 余额 = 余额 + @金额 WHERE 账户号 = @转入账户;
        
        COMMIT;
        RETURN 0;  -- 成功
    END TRY
    BEGIN CATCH
        ROLLBACK;
        RETURN -1;  -- 系统错误
    END CATCH;
END;

执行存储过程

复制代码
-- 执行简单存储过程
EXEC GetStudentsByDept 'CS';

-- 执行带输出参数的存储过程
DECLARE @人数 INT;
EXEC CountStudentsByDept 'CS', @人数 OUTPUT;
PRINT '该系人数:' + CAST(@人数 AS VARCHAR);

-- 执行转账存储过程
DECLARE @结果 INT;
EXEC @结果 = TransferMoney 'A001', 'B002', 1000.00;
IF @结果 = 0
    PRINT '转账成功';
ELSE IF @结果 = 1
    PRINT '余额不足';
ELSE
    PRINT '系统错误';

删除存储过程

复制代码
DROP PROCEDURE GetStudentsByDept;

七、触发器(Trigger)

7.1 是什么?

触发器 = 一种特殊的存储过程 ,不由用户显式调用,而是在特定事件(INSERT/UPDATE/DELETE)发生时自动触发执行

下辖知识点

知识点 是什么
DML 触发器 由 INSERT/UPDATE/DELETE 触发
DDL 触发器 由 CREATE/ALTER/DROP 等触发
AFTER 触发器 在操作完成后触发(SQL Server 默认)
INSTEAD OF 触发器 替代原始操作执行(常用于视图更新)
BEFORE 触发器 在操作执行前触发(MySQL/Oracle 支持)
INSERTED 表 存放刚插入/更新后的新数据(内存中的虚拟表)
DELETED 表 存放刚删除/更新前的旧数据(内存中的虚拟表)
行级触发器 每行触发一次
语句级触发器 每条语句触发一次
递归/嵌套触发器 触发器触发另一个触发器
ENABLE/DISABLE 启用/禁用触发器

7.2 为什么要有触发器?

有些操作必须在数据变更时自动发生,不能靠人工:

场景 触发器作用
插入订单时,自动扣减库存 INSERT 触发器自动更新库存表
删除员工时,自动删除其家属记录 DELETE 触发器级联删除
修改账户余额时,自动记录审计日志 UPDATE 触发器写审计表
防止非法时间段的修改 BEFORE 触发器检查并拒绝
复杂约束无法用 CHECK 表达 用触发器写任意逻辑判断
数据同步 主表修改时,触发器同步更新从表/缓存

核心价值

  1. 自动化 ------ 事件驱动,不用手动调用。

  2. 数据一致性 ------ 关联表之间的级联操作自动完成。

  3. 审计追踪 ------ 谁在什么时候改了什么,自动记录。

  4. 复杂约束 ------ CHECK 不够用时,触发器可以写任意逻辑。

但注意 :触发器是双刃剑,过多或过于复杂会降低性能、增加维护难度。


7.3 怎么用?

创建触发器

复制代码
-- 触发器1:插入学生时,自动初始化选课统计表
CREATE TRIGGER trg_InsertStudent
ON 学生
AFTER INSERT
AS
BEGIN
    INSERT INTO 学生统计 (学号, 选课门数, 总学分)
    SELECT 学号, 0, 0 FROM INSERTED;
END;

-- 触发器2:删除学生时,级联删除其选课记录
CREATE TRIGGER trg_DeleteStudent
ON 学生
INSTEAD OF DELETE
AS
BEGIN
    -- 先删选课记录
    DELETE FROM 选课 WHERE 学号 IN (SELECT 学号 FROM DELETED);
    -- 再删学生
    DELETE FROM 学生 WHERE 学号 IN (SELECT 学号 FROM DELETED);
END;

-- 触发器3:修改成绩时,记录审计日志
CREATE TRIGGER trg_AuditGrade
ON 选课
AFTER UPDATE
AS
BEGIN
    IF UPDATE(成绩)
    BEGIN
        INSERT INTO 成绩变更日志 (学号, 课程号, 旧成绩, 新成绩, 修改时间, 操作人)
        SELECT 
            DELETED.学号,
            DELETED.课程号,
            DELETED.成绩,
            INSERTED.成绩,
            GETDATE(),
            SUSER_SNAME()
        FROM DELETED JOIN INSERTED 
            ON DELETED.学号 = INSERTED.学号 
            AND DELETED.课程号 = INSERTED.课程号;
    END;
END;

INSERTED 和 DELETED 虚拟表

操作 INSERTED DELETED
INSERT 包含刚插入的新行
DELETE 包含刚删除的旧行
UPDATE 包含更新后的新行 包含更新前的旧行

禁用/启用触发器

复制代码
-- 禁用触发器(批量导入数据时临时禁用)
DISABLE TRIGGER trg_AuditGrade ON 选课;

-- 批量操作...

-- 重新启用
ENABLE TRIGGER trg_AuditGrade ON 选课;

触发器 vs 约束 vs 存储过程 对比

特性 触发器 约束 存储过程
执行时机 自动(事件驱动) 自动(数据变更时检查) 手动调用
主要用途 级联操作、审计、复杂规则 保证数据完整性 封装业务逻辑
灵活性 最高(任意逻辑) 低(固定规则) 高(任意逻辑)
性能影响 较大 可控
是否透明 对应用透明 对应用透明 应用需显式调用

八、知识脉络图

复制代码
SQL 数据定义、更新及数据库编程
    │
    ├── DDL(建骨架)
    │       ├── 是什么:CREATE/ALTER/DROP/TRUNCATE
    │       ├── 为什么:定义结构、加约束、建索引、保证质量
    │       └── 怎么用:建表、改表、删表、加索引、建模式
    │
    ├── DML(填内容)
    │       ├── 是什么:INSERT/UPDATE/DELETE/MERGE
    │       ├── 为什么:数据增删改、批量导入、集合操作
    │       └── 怎么用:单行/多行插入、带子查询更新、条件删除
    │
    ├── 视图(虚拟表)
    │       ├── 是什么:从基本表导出的虚拟表
    │       ├── 为什么:简化查询、安全隔离、逻辑独立、性能优化
    │       └── 怎么用:CREATE VIEW、可更新视图、WITH CHECK OPTION、物化视图
    │
    ├── T-SQL(过程化编程)
    │       ├── 是什么:SQL Server 的过程化扩展
    │       ├── 为什么:变量、分支、循环、异常处理、事务控制
    │       └── 怎么用:DECLARE/SET、IF...ELSE/WHILE/CASE、TRY...CATCH
    │
    ├── 游标(逐行处理)
    │       ├── 是什么:逐行遍历结果集的机制
    │       ├── 为什么:集合操作无法处理的逐行场景
    │       └── 怎么用:DECLARE/OPEN/FETCH/CLOSE/DEALLOCATE
    │
    ├── 存储过程(预编译代码块)
    │       ├── 是什么:预编译并存储的 SQL 过程
    │       ├── 为什么:复用、性能、安全、减少网络传输
    │       └── 怎么用:CREATE PROCEDURE、参数、返回值、EXEC
    │
    └── 触发器(事件驱动自动执行)
            ├── 是什么:INSERT/UPDATE/DELETE 时自动触发的特殊过程
            ├── 为什么:自动化、级联操作、审计、复杂约束
            └── 怎么用:AFTER/INSTEAD OF、INSERTED/DELETED、ENABLE/DISABLE

九、一句话记忆

概念 一句话
DDL 建骨架:CREATE 建、ALTER 改、DROP 删
DML 填内容:INSERT 插、UPDATE 改、DELETE 删
视图 虚拟表,不存数据,只存查询定义
可更新视图 简单的视图可以直接改底层表
物化视图 把视图结果存下来,查起来飞快
T-SQL SQL + 变量 + 分支 + 循环 + 异常处理
游标 一行一行啃结果集,性能差但有时候必须用
存储过程 把一堆 SQL 打包存数据库里,随叫随到
触发器 表一有动静就自动执行的"暗器"
INSERTED 表 触发器里看新数据的窗口
DELETED 表 触发器里看旧数据的窗口
AFTER 触发器 改完后再执行
INSTEAD OF 触发器 把原来的操作替换掉
触发器双刃剑 用多了性能差,调试难,谨慎用

总结基于《数据库系统概论》第3章扩展及数据库编程知识体系整理

相关推荐
Mortalbreeze3 小时前
深度理解文件系统 ---- 从磁盘存储到内核存储
大数据·linux·数据库
2301_803934613 小时前
MySQL 字段类型选择规范指南
jvm·数据库·python
oddsand13 小时前
Redis网络模型
java·数据库·redis
皮卡祺q3 小时前
【redies0-导论】分布式系统的演进-引进redis原因
java·数据库·redis
南极企鹅4 小时前
事务&@Transactional注解
java·数据库·spring·oracle·mybatis
UrSpecial4 小时前
Redis与多线程
数据库·redis·缓存
bqq198610264 小时前
MySQL 8与MySQL 5.7的主要区别
数据库·mysql
chushiyunen4 小时前
r树索引、mysql对r树的支持
数据库·mysql
会编程的土豆4 小时前
Redis Sorted Set(有序集合)详解
数据库·redis·bootstrap