数据库系统原理 · 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...ELSE、WHILE、CASE、GOTO、RETURN
批处理 GO 分隔批处理
注释 -- 单行 和 /* 多行 */
系统函数 GETDATE()、LEN()、CONVERT() 等
TRY...CATCH 异常处理
事务控制 BEGIN TRAN、COMMIT、ROLLBACK、SAVEPOINT

4.2 为什么要有 T-SQL?

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

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

核心价值:

  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 PROCEDURE、DROP 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章扩展及数据库编程知识体系整理

相关推荐
JavaEdge.12 分钟前
设定索引方法和检索设置
数据库
小蒜学长15 分钟前
基于Vue.js + Spring Boot的酒店客房预订系统设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·酒店客房预订系统
墨者阳16 分钟前
KingbaseES V9R2C16 深度实测:64位事务ID、内存防护、集群备份与坏块修复四大能力全解析
数据库
事圆则缓22 分钟前
聊天与引用回复的 SQL 怎么设计
数据库·sql
APItesterCris22 分钟前
告别反复手工查价:用 Open‑Claw 搭建商品监控与数据分析小工具
大数据·数据库·数据仓库·自动化
来福猿27 分钟前
分布式锁 Redis 实现源码分析:SETNX、过期时间与可重入设计
数据库·redis·分布式
xiangyun6132 分钟前
C++ list 模拟实现(一):手撕双向链表,从节点到迭代器
数据库·c++·算法·链表·stl·list
wespaul36 分钟前
一条自然语言命令的 49 个节点:可视化编排与每段强校验
前端·数据库
KIDULT°9 小时前
MySQL系列 (1):数据库原理
运维·数据库·mysql·云计算
我不是程序员三三9 小时前
透明加密软件偶发解密异常排错实录:文件时能打开时乱码
数据库·域智盾