数据库基础知识点总结

第一章 SQLPLUS 使用与服务管理

1.1 核心服务启动

Oracle 运行需启动两个核心服务,两种启动方式:

  • 方法 1(图形界面) :控制面板→服务→启动以下服务:

    • OracleServiceORCL(数据库实例服务)
    • OracleTNSListener(监听服务)
  • 方法 2(命令行,管理员权限)

    bash

    运行

    复制代码
    # 启动服务
    net start OracleServiceORCL
    lsnrctl start
    # 停止服务
    net stop OracleServiceORCL
    lsnrctl stop

1.2 SQLPLUS 连接方式

1.2.1 常规连接步骤
  1. 打开 CMD,输入sqlplus进入交互界面;
  2. 输入用户名和口令,格式:用户名/密码 [as 身份](sys 用户必须指定as sysdba)。
1.2.2 常用用户与口令
用户名 角色 默认口令 说明
sys 超级管理员 change_on_install/Oracle11 必须以as sysdba身份登录
system 普通管理员 manager/Oracle11 管理数据库日常操作
scott 普通用户 tiger 10g 后默认锁定,需解锁
sh 测试用户 sh 包含百万级数据的表
1.2.3 scott 用户解锁流程
  1. 以 sys 用户登录: sql

    复制代码
    sqlplus sys/Oracle11 as sysdba
  2. 确认当前用户: sql

    复制代码
    show user; -- 应显示"SYS"
  3. 解锁并重置 scott 密码: sql

    复制代码
    alter user scott identified by tiger account unlock; -- 解锁并设密码为tiger
  4. 切换到 scott 用户: sql

    复制代码
    connect scott/tiger;
    show user; -- 应显示"SCOTT"
1.2.4 一键进入 scott 用户(技巧)

创建批处理文件(scott_login.bat),内容如下:

bash

运行

复制代码
@echo off
:: 启动核心服务
net start OracleServiceORCL
lsnrctl start
:: 连接scott用户(直接输入密码,避免交互)
sqlplus scott/tiger

双击运行即可一键登录(需确保服务未启动时使用)。

第二章 SQL 基础

2.1 SQL 分类

分类 英文缩写 功能 常用命令
数据查询 DQL 查询表数据 select
数据定义 DDL 创建 / 修改 / 删除数据库对象 create, alter, drop
数据修改 DML 增删改表数据 insert, update, delete
数据控制 DCL 权限管理与事务控制 grant, revoke, commit, rollback

2.2 基础查询操作

2.2.1 查看用户表

sql

复制代码
select * from tab; -- 显示当前用户所有表(tab是系统伪表)
2.2.2 查看表结构

sql

复制代码
desc 表名; -- 例:desc emp; 显示emp表的列名、类型、约束
2.2.3 基本查询语法

sql

复制代码
select [列名1, 列名2, ... | *] -- *表示查询所有列
from 表名
[where 条件] -- 筛选数据
[group by 分组列] -- 分组统计
[having 分组条件] -- 筛选分组结果
[order by 排序列 [asc|desc]]; -- 排序(asc升序,默认;desc降序)
2.2.4 常用查询技巧
  1. 查询所有列

    sql

    复制代码
    select * from emp; -- 查询emp表所有数据
  2. 查询特定列

    sql

    复制代码
    select ename, sal, hiredate from emp; -- 查询员工姓名、工资、入职日期
  3. 去重查询distinct):

    sql

    复制代码
    select distinct deptno from emp; -- 查询不重复的部门号
  4. 字符串连接||):

    sql

    复制代码
    select ename || '的工资是:' || sal as 工资信息 from emp; -- 自定义输出格式

第三章 条件查询与排序

3.1 条件查询(where 子句)

3.1.1 比较运算符
运算符 含义 示例
= 等于 deptno = 10
!=/<> 不等于 sal != 5000
>/< 大于 / 小于 hiredate > '01-1 月 - 1982'
>=/<= 大于等于 / 小于等于 sal >= 3000
3.1.2 逻辑运算符(优先级:not > and > or)

sql

复制代码
-- 示例:查询10号部门的经理或20号部门的职员
select * from emp where (deptno=10 and job='MANAGER') or (deptno=20 and job='CLERK');
3.1.3 模糊查询(like)
  • _:匹配任意 1 个字符;
  • %:匹配任意 0 个或多个字符。

sql

复制代码
-- 示例1:查询姓名包含'A'的员工
select * from emp where ename like '%A%';
-- 示例2:查询姓名第2个字符是'A'的员工
select * from emp where ename like '_A%';
3.1.4 范围查询(in /between and)
  • in:匹配列表中的任意一个值;
  • between and:匹配连续范围内的值(闭区间)。

sql

复制代码
-- 示例1:查询20、30号部门的员工
select * from emp where deptno in (20, 30);
-- 示例2:查询工资在2000-3000之间的员工
select * from emp where sal between 2000 and 3000; -- 等价于sal>=2000 and sal<=3000
3.1.5 空值处理(is null /is not null)

空值(null)不等于任何值,需用专用运算符判断:

sql

复制代码
-- 示例:查询没有奖金的员工(comm为空)
select * from emp where comm is null;
-- 示例:查询有奖金的员工
select * from emp where comm is not null;

3.2 排序(order by)

sql

复制代码
-- 示例1:按工资升序排列(默认asc)
select * from emp order by sal;
-- 示例2:按工资降序排列,工资相同按入职日期升序
select * from emp order by sal desc, hiredate asc;

第四章 函数

4.1 单行函数

4.1.1 字符串函数
函数 功能 示例
upper(str) 转换为大写 upper(ename) → 'SMITH'
lower(str) 转换为小写 lower(ename) → 'smith'
initcap(str) 首字母大写,其余小写 initcap(ename) → 'Smith'
instr(str1, str2[,m,n]) 从 str1 的第 m 位开始,找 str2 第 n 次出现的位置 instr('Oracle', 'a', 1, 1) → 2
substr(str, m[,n]) 截取 str 从第 m 位开始的 n 个字符(n 省略则取到末尾) substr('Oracle', 2, 3) → 'rac'
4.1.2 日期函数
函数 功能 示例
sysdate 获取当前系统日期时间 select sysdate from dual;
months_between(d1, d2) 计算 d1 与 d2 相差的月数 months_between (sysdate, hiredate)/12 → 工龄(年)
to_char (d, ' 格式 ') 日期转字符串 to_char(sysdate, 'yyyy-mm-dd hh:mi:ss') → '2024-05-20 14:30:00'
to_date (str, ' 格式 ') 字符串转日期 to_date ('2024-05-20', 'yyyy-mm-dd') → 日期类型
4.1.3 数值函数
函数 功能 示例
round(n, m) 四舍五入(m 为小数位数) round(3.1415, 2) → 3.14
trunc(n, m) 截断(不四舍五入) trunc(3.1415, 2) → 3.14
mod(n, m) 求余 mod(10, 3) → 1
floor(n) 向下取整 floor(3.9) → 3
ceil(n) 向上取整 ceil(3.1) → 4
4.1.4 空值处理函数
函数 功能 示例
nvl(a, b) a 为空则返回 b,否则返回 a nvl (comm, 0) → 奖金为空时按 0 计算
nvl2(a, b, c) a 不为空返回 b,为空返回 c nvl2 (comm, sal+comm, sal) → 有奖金则加奖金

4.2 分组函数(多行函数)

4.2.1 常用分组函数
函数 功能 示例
count(*) 统计总行数(包含空值) select count (*) from emp; → 员工总数
count (列名) 统计该列非空值的行数 select count (comm) from emp; → 有奖金的员工数
sum (列名) 求和(仅数值型) select sum (sal) from emp; → 工资总和
avg (列名) 求平均值(仅数值型) select avg (sal) from emp; → 平均工资
max (列名) 求最大值 select max (sal) from emp; → 最高工资
min (列名) 求最小值 select min (sal) from emp; → 最低工资
4.2.2 分组函数使用注意事项
  1. 分组函数忽略空值(count(*)除外);
  2. sumavg仅支持数值型列;
  3. 若查询中同时包含普通列和分组函数,普通列必须放在group by子句中;
  4. 分组后的筛选用having子句(不能用wherewhere在分组前筛选)。
4.2.3 示例:分组查询

sql

复制代码
-- 示例1:按部门分组,统计每个部门的员工数和平均工资
select deptno, count(empno) as 员工数, avg(sal) as 平均工资
from emp
group by deptno;

-- 示例2:查询员工数超过3人的部门
select deptno, count(empno) as 员工数
from emp
group by deptno
having count(empno) > 3; -- having筛选分组结果

第五章 多表查询

5.1 多表查询分类

连接类型 功能 语法特点
内连接 仅返回满足连接条件的记录 where指定连接条件(等值 / 不等值)
外连接 返回满足条件的记录 + 部分不满足条件的记录 (+)运算符(Oracle 专属)或 SQL99 语法
自连接 同一表内的连接(模拟多表) 表别名区分不同 "角色"

5.2 内连接

5.2.1 等值连接(最常用)

条件:两表有公共列(数据类型一致),连接条件为公共列相等。

sql

复制代码
-- 示例:查询每个员工的工号、姓名、部门名(emp表和dept表连接)
select e.empno, e.ename, d.dname
from emp e, dept d -- e和d是表别名(简化书写)
where e.deptno = d.deptno; -- 连接条件(公共列deptno)
5.2.2 不等连接

条件:两表无公共列,连接条件为非等值比较。

sql

复制代码
-- 示例:查询每个员工的工资等级(emp表和salgrade表连接)
select e.ename, e.sal, s.grade
from emp e, salgrade s
where e.sal between s.losal and s.hisal; -- 不等连接条件(工资在等级范围内)
5.2.3 自连接

条件:同一表中不同行之间的连接(需用别名区分)。

sql

复制代码
-- 示例:查询每个员工的姓名及其经理的姓名(emp表自连接)
select e.ename as 员工姓名, m.ename as 经理姓名
from emp e, emp m -- e:员工表;m:经理表
where e.mgr = m.empno; -- 连接条件(员工的经理号=经理的工号)

5.3 外连接

外连接会保留其中一个表的所有记录,另一个表不满足条件的记录用null填充。Oracle 用(+)表示 "信息缺失的一方"。

5.3.1 左外连接(保留左表所有记录)

sql

复制代码
-- 示例:查询所有员工的姓名及其经理姓名(包括无经理的员工)
select e.ename as 员工姓名, m.ename as 经理姓名
from emp e, emp m
where e.mgr = m.empno(+); -- m表是信息缺失方(无经理时m.ename为null)
5.3.2 右外连接(保留右表所有记录)

sql

复制代码
-- 示例:查询所有部门的名称及其员工数(包括无员工的部门)
select d.dname as 部门名, count(e.empno) as 员工数
from emp e, dept d
where e.deptno(+) = d.deptno -- e表是信息缺失方(无员工时count为0)
group by d.dname;

5.4 SQL99 连接语法(标准语法)

sql

复制代码
-- 内连接
select e.ename, d.dname
from emp e
inner join dept d on e.deptno = d.deptno;

-- 左外连接
select e.ename, m.ename
from emp e
left outer join emp m on e.mgr = m.empno;

-- 右外连接
select d.dname, count(e.empno)
from emp e
right outer join dept d on e.deptno = d.deptno
group by d.dname;

第六章 子查询

6.1 子查询分类

分类 功能 特点
单行子查询 返回单列单值 可用=><等简单比较运算符
多行子查询 返回单列多值 需用inanyall等多行运算符
多列子查询 返回多列多值 用于复杂条件匹配
相关子查询 子查询依赖父查询的结果 逐行执行,效率较低

6.2 单行子查询

sql

复制代码
-- 示例:查询比7788号员工工资高的员工信息
select * from emp
where sal > (select sal from emp where empno = 7788); -- 子查询返回7788的工资(单值)

6.3 多行子查询

6.3.1 in运算符(匹配列表中的任意值)

sql

复制代码
-- 示例:查询20号或30号部门的员工信息
select * from emp
where deptno in (select deptno from dept where dname in ('RESEARCH', 'SALES'));
6.3.2 any/all运算符(与比较运算符结合)
  • any:匹配列表中任意一个值(等价于in);
  • all:匹配列表中所有值。

sql

复制代码
-- 示例1:查询比20号部门任意员工工资高的员工(> any 等价于 > 最小值)
select * from emp
where sal > any (select sal from emp where deptno = 20);

-- 示例2:查询比20号部门所有员工工资高的员工(> all 等价于 > 最大值)
select * from emp
where sal > all (select sal from emp where deptno = 20);

6.4 子查询应用:获取特定行记录

利用rownum(伪列,行号)和子查询获取第 N 行或指定范围的记录:

sql

复制代码
-- 示例1:获取emp表第4、6、8、10行记录
select * from (select rownum as rn, emp.* from emp) a
where a.rn in (4, 6, 8, 10);

-- 示例2:获取emp表第5-10行记录
select * from (select rownum as rn, emp.* from emp) a
where a.rn between 5 and 10;

第七章 DDL 与表约束

7.1 表的创建

7.1.1 直接创建表

sql

复制代码
-- 示例:创建学生表student(学号、姓名、性别、出生日期、入学时间)
create table student (
    s_no char(11) primary key, -- 学号(主键,11位)
    s_name varchar2(20) not null, -- 姓名(非空)
    s_gender char(1) check (s_gender in ('F', 'M')), -- 性别(只能是F/M)
    s_birth date not null, -- 出生日期(非空)
    s_come date -- 入学时间(可空)
);
7.1.2 基于查询创建表(复制表结构 + 数据)

sql

复制代码
-- 示例1:复制dept表到dept_bak(含数据)
create table dept_bak as select * from dept;

-- 示例2:复制emp表的部门号和平均工资到t_avgsal(仅结构+统计数据)
create table t_avgsal as 
select deptno, avg(sal) as avgsal from emp group by deptno;

7.2 表的约束

约束用于保证数据完整性,常用约束类型如下:

约束类型 关键字 功能 示例
主键约束 primary key 唯一标识行,非空且唯一 s_no char(11) primary key
唯一约束 unique 列值唯一,可空 s_name varchar2(20) unique
非空约束 not null 列值不能为空 s_birth date not null
检查约束 check 列值满足指定条件 s_gender check (s_gender in ('F','M'))
外键约束 foreign key 引用其他表的主键 / 唯一键 s_no char(11) references student(s_no)
7.2.1 约束的维护

sql

复制代码
-- 1. 添加约束(表级约束)
alter table score add constraint fk_s_no foreign key (s_no) references student(s_no);

-- 2. 删除约束
alter table score drop constraint fk_s_no;

-- 3. 禁用约束(临时失效)
alter table score disable constraint fk_s_no;

-- 4. 启用约束(恢复生效)
alter table score enable constraint fk_s_no;
7.2.2 查看约束

sql

复制代码
-- 查看当前用户所有表的约束
select constraint_name, constraint_type, table_name 
from user_constraints;

-- 查看student表的约束
select constraint_name, constraint_type 
from user_constraints 
where table_name = 'STUDENT';

7.3 表结构的修改

sql

复制代码
-- 1. 添加列
alter table student add (s_address varchar2(50)); -- 新增地址列

-- 2. 修改列属性(类型/长度,需注意数据兼容性)
alter table student modify s_address char(100); -- 地址列改为char(100)

-- 3. 删除列
alter table student drop column s_address; -- 删除地址列

-- 4. 修改表名
rename student to stu; -- 表名改为stu

第八章 视图、索引与序列号

8.1 视图

视图是基于表的 "虚表",不存储数据,仅保存查询逻辑,用于简化复杂查询和控制数据访问。

8.1.1 创建视图

sql

复制代码
-- 示例1:创建视图view_emp_dept(员工姓名、部门名、工资)
create view view_emp_dept as
select e.ename, d.dname, e.sal
from emp e, dept d
where e.deptno = d.deptno;

-- 示例2:创建只读视图(禁止DML操作)
create view view_dept as
select * from dept with read only;

-- 示例3:创建带check option的视图(修改数据需满足where条件)
create view view_emp_20 as
select * from emp where deptno = 20 with check option; -- 只能修改20号部门的员工
8.1.2 视图的使用

sql

复制代码
-- 查询视图(与查询表一致)
select * from view_emp_dept where sal > 3000;

-- 修改视图(仅非只读、带check option且满足条件时可用)
update view_emp_20 set sal = 3500 where ename = 'SMITH';

8.2 索引

索引用于提升查询速度,通过建立 "索引树" 减少全表扫描,但会占用存储空间,且 DML 操作时需维护索引(降低写入效率)。

8.2.1 创建索引

sql

复制代码
-- 示例1:创建单列索引(emp表的ename列)
create index idx_emp_ename on emp(ename);

-- 示例2:创建复合索引(emp表的deptno+sal列,按deptno升序、sal降序)
create index idx_emp_deptno_sal on emp(deptno, sal desc);

-- 示例3:创建基于函数的索引(解决模糊查询效率问题)
create index idx_emp_lower_ename on emp(lower(ename)); -- 用于lower(ename)的查询
8.2.2 索引的使用与维护

sql

复制代码
-- 查看索引(当前用户所有索引)
select index_name, table_name, column_name 
from user_ind_columns 
where table_name = 'EMP';

-- 重建索引(索引碎片化后提升性能)
alter index idx_emp_ename rebuild;

-- 删除索引
drop index idx_emp_ename;
8.2.3 索引使用建议
  1. 表数据量大(万级以上)时创建索引;
  2. 列值重复率低(如主键、唯一列)适合建索引;
  3. 频繁查询的列(where、join、order by 中的列)建索引;
  4. 避免在频繁更新的列上建索引(维护成本高)。

8.3 序列号

序列号用于生成唯一的自增数字,常用于主键列的自动赋值。

8.3.1 创建序列号

sql

复制代码
-- 示例:创建序列号seq_emp,从1开始,每次增1,无最大值
create sequence seq_emp
start with 1 -- 起始值
increment by 1 -- 增量
nomaxvalue -- 无最大值
nominvalue -- 无最小值
cache 20; -- 缓存20个值(提升性能)
8.3.2 序列号的使用

序列号的两个核心伪列:

  • nextval:获取下一个序列号(首次使用需用 nextval 初始化);
  • currval:获取当前序列号(需先调用 nextval)。

sql

复制代码
-- 示例:用序列号给emp表插入新员工(主键empno自动生成)
insert into emp(empno, ename, job, deptno)
values(seq_emp.nextval, 'LISI', 'CLERK', 20); -- nextval生成下一个序列号

-- 查看当前序列号
select seq_emp.currval from dual; -- 返回刚插入的empno

第九章 PL/SQL 基础

PL/SQL(Procedural Language/SQL)是 Oracle 的过程化编程语言,结合了 SQL 的查询能力和编程语言的流程控制(条件、循环等)。

9.1 PL/SQL 块结构

PL/SQL 程序由 "块" 组成,每个块包含 3 个部分(声明部分和异常处理部分可选):

sql

复制代码
declare
    -- 声明部分:定义变量、常量、游标等(可选)
    变量名 数据类型 [:= 初值];
begin
    -- 执行部分:核心逻辑(必须)
    SQL语句;
    PL/SQL语句;
exception
    -- 异常处理部分:处理执行过程中的错误(可选)
    when 异常类型 then 处理逻辑;
end;
/ -- 结束符(必须)

9.2 变量声明与赋值

9.2.1 普通变量

sql

复制代码
-- 示例:输出"Hello PL/SQL"
set serveroutput on; -- 开启屏幕打印(必须)
declare
    v_msg varchar2(20) := 'Hello PL/SQL'; -- 声明变量并赋值
begin
    dbms_output.put_line(v_msg); -- 打印变量
end;
/
9.2.2 引用型变量(%type

引用已有表列的数据类型,避免硬编码:

sql

复制代码
-- 示例:查询7788号员工的姓名和工资
declare
    v_ename emp.ename%type; -- 引用emp表ename列的类型
    v_sal emp.sal%type; -- 引用emp表sal列的类型
begin
    select ename, sal into v_ename, v_sal from emp where empno = 7788;
    dbms_output.put_line('姓名:' || v_ename || ',工资:' || v_sal);
end;
/
9.2.3 记录型变量(%rowtype

引用整个表的行结构,用于存储一行数据:

sql

复制代码
-- 示例:查询30号部门的所有信息
declare
    v_dept dept%rowtype; -- 引用dept表的行结构
begin
    select * into v_dept from dept where deptno = 30;
    dbms_output.put_line('部门号:' || v_dept.deptno || ',部门名:' || v_dept.dname);
end;
/

9.3 流程控制

9.3.1 条件判断(if 语句)

sql

复制代码
-- 示例:根据工资判断员工等级
declare
    v_sal emp.sal%type;
begin
    select sal into v_sal from emp where empno = 7788;
    if v_sal > 3000 then
        dbms_output.put_line('高工资');
    elsif v_sal between 1500 and 3000 then
        dbms_output.put_line('中等工资');
    else
        dbms_output.put_line('低工资');
    end if;
end;
/
9.3.2 循环(loop/while/for)

sql

复制代码
-- 示例1:loop循环(计算1+2+...+100)
declare
    v_i number := 1;
    v_sum number := 0;
begin
    loop
        v_sum := v_sum + v_i;
        v_i := v_i + 1;
        exit when v_i > 100; -- 退出条件
    end loop;
    dbms_output.put_line('1+2+...+100=' || v_sum);
end;
/

-- 示例2:for循环(计算1-100的偶数和)
declare
    v_sum number := 0;
begin
    for v_i in 2..100 loop -- 步长1,2到100
        if mod(v_i, 2) = 0 then -- 判断偶数
            v_sum := v_sum + v_i;
        end if;
    end loop;
    dbms_output.put_line('1-100偶数和=' || v_sum);
end;
/

第十章 游标

游标用于临时存储查询结果集,解决 "select into" 只能返回单行的问题,分为隐式游标和显式游标。

10.1 隐式游标

系统自动创建的游标,用于执行select into、DML(insert/update/delete)语句,游标名为sql

10.1.1 隐式游标属性
属性 功能 示例
%found 操作成功(如查询到数据、DML 影响行数 > 0)返回 true if sql%found then commit; end if;
%notfound 操作失败返回 true if sql%notfound then rollback; end if;
%rowcount DML 操作影响的行数 dbms_output.put_line (' 更新行数:' sql%rowcount);
%isopen 游标是否打开(隐式游标始终为 false) 无实际使用场景
10.1.2 示例:隐式游标判断更新是否成功

sql

复制代码
begin
    update emp set sal = 3500 where empno = 1234; -- 假设1234号员工不存在
    if sql%found then
        dbms_output.put_line('更新成功,影响行数:' || sql%rowcount);
        commit;
    else
        dbms_output.put_line('更新失败,未找到员工');
        rollback;
    end if;
end;
/

10.2 显式游标

用户自定义的游标,用于处理多行查询结果,需手动完成 "声明→打开→提取→关闭" 四步。

10.2.1 显式游标使用步骤

sql

复制代码
-- 示例:查询20号部门的所有员工姓名和入职日期
declare
    -- 1. 声明游标(定义查询逻辑)
    cursor cur_emp is
        select ename, hiredate from emp where deptno = 20;
    -- 声明变量存储提取的数据(与游标查询列对应)
    v_ename emp.ename%type;
    v_hiredate emp.hiredate%type;
begin
    -- 2. 打开游标(执行查询,加载结果集到内存)
    open cur_emp;
    -- 3. 提取数据(循环提取所有行)
    loop
        fetch cur_emp into v_ename, v_hiredate; -- 提取一行到变量
        exit when cur_emp%notfound; -- 无数据时退出循环
        -- 处理数据
        dbms_output.put_line('姓名:' || v_ename || ',入职日期:' || to_char(v_hiredate, 'yyyy-mm-dd'));
    end loop;
    -- 4. 关闭游标(释放内存)
    close cur_emp;
end;
/
10.2.2 显式游标简化(for 循环)

for 循环会自动完成 "打开→提取→关闭",无需手动操作:

sql

复制代码
-- 示例:用for循环查询20号部门员工
declare
    cursor cur_emp is
        select ename, hiredate from emp where deptno = 20;
begin
    for rec_emp in cur_emp loop -- rec_emp是记录型变量,自动匹配游标列
        dbms_output.put_line('姓名:' || rec_emp.ename || ',入职日期:' || to_char(rec_emp.hiredate, 'yyyy-mm-dd'));
    end loop;
end;
/
10.2.3 带参数的游标

游标可接收参数,动态调整查询条件:

sql

复制代码
-- 示例:查询指定部门的员工(参数为部门号)
declare
    cursor cur_emp(p_deptno number) is -- p_deptno是参数
        select ename, sal from emp where deptno = p_deptno;
begin
    for rec_emp in cur_emp(30) loop -- 传入参数30(查询30号部门)
        dbms_output.put_line('姓名:' || rec_emp.ename || ',工资:' || rec_emp.sal);
    end loop;
end;
/

第十一章 触发器

触发器是一种特殊的 PL/SQL 程序,在满足 "特定事件" 时自动执行,用于实现复杂的数据完整性、同步备份、日志记录等功能。

11.1 触发器分类

分类 触发事件 作用对象 示例场景
DML 触发器 insert/update/delete 限制工资修改幅度、级联更新
替代触发器 insert/update/delete 视图 实现视图的 DML 操作
DDL 触发器 create/alter/drop 模式(用户) 禁止删除表、记录 DDL 操作日志
数据库触发器 数据库启动 / 关闭、用户登录 / 退出 数据库 数据库启动时自动执行备份

11.2 DML 触发器(最常用)

11.2.1 核心概念
  • 触发时间before(事件前执行)或after(事件后执行);
  • 触发事件insertupdatedelete(可组合);
  • 触发级别for each row(行级触发器,每影响一行执行一次)或for each statement(语句级触发器,整个语句执行一次);
  • 谓词inserting(是否执行 insert)、updating(是否执行 update)、deleting(是否执行 delete);
  • 伪记录 :old(操作前的数据)、 :new(操作后的数据),仅行级触发器可用。
11.2.2 示例 1:限制工资修改幅度(行级触发器)

sql

复制代码
-- 需求:员工工资修改幅度不能超过300
create or replace trigger tri_emp_sal_check
before update of sal -- 触发时间:更新前;触发事件:更新sal列
on emp
for each row -- 行级触发器
begin
    -- 判断修改幅度是否超过300(abs取绝对值)
    if abs(:new.sal - :old.sal) > 300 then
        -- 抛出自定义异常(-20000~-20999为用户自定义异常码)
        raise_application_error(-20001, '工资修改幅度不能超过300!');
    end if;
end;
/

-- 测试:修改7788号员工工资(幅度300以内,成功)
update emp set sal = 3300 where empno = 7788; -- 原工资3000

-- 测试:修改幅度400(失败,触发异常)
update emp set sal = 3400 where empno = 7788;
11.2.3 示例 2:级联更新(行级触发器)

sql

复制代码
-- 需求:当dept表的deptno修改时,自动更新emp表对应的deptno(解决外键约束限制)
create or replace trigger tri_deptno_cascade
after update of deptno -- 触发时间:更新后;触发事件:更新deptno列
on dept
for each row -- 行级触发器
begin
    -- 级联更新emp表的deptno
    update emp set deptno = :new.deptno where deptno = :old.deptno;
end;
/

-- 测试:将10号部门改为11号(emp表中10号部门的员工会自动改为11号)
update dept set deptno = 11 where deptno = 10;
11.2.4 示例 3:同步备份(行级触发器)

sql

复制代码
-- 需求:dept表的insert/update/delete操作自动同步到dept_bak表
-- 1. 先创建备份表dept_bak
create table dept_bak as select * from dept;

-- 2. 创建触发器
create or replace trigger tri_dept_bak
after insert or update or delete -- 触发事件:insert/update/delete
on dept
for each row -- 行级触发器
begin
    if inserting then
        -- 插入操作:同步插入到dept_bak
        insert into dept_bak values(:new.deptno, :new.dname, :new.loc);
    elsif updating then
        -- 更新操作:同步更新dept_bak
        update dept_bak set dname = :new.dname, loc = :new.loc where deptno = :old.deptno;
    else
        -- 删除操作:同步删除dept_bak
        delete from dept_bak where deptno = :old.deptno;
    end if;
end;
/

-- 测试:插入新部门(dept_bak会自动同步)
insert into dept(deptno, dname, loc) values(50, 'FINANCE', 'CHICAGO');

-- 测试:更新部门名称(dept_bak会自动同步)
update dept set dname = 'FINANCE_DEPT' where deptno = 50;

-- 测试:删除部门(dept_bak会自动同步)
delete from dept where deptno = 50;

第十二章 实验技巧与常见问题

12.1 实验过程记录(spool 命令)

sql

复制代码
-- 开启spool(记录到D盘的oracle_log.txt,append表示追加,不覆盖)
spool d:/oracle_log.txt append

-- 执行实验操作(示例)
select * from emp;
update emp set sal = 3500 where ename = 'SCOTT';
commit;

-- 关闭spool(必须执行,否则文件不完整)
spool off

12.2 SQL 错误快速修改(edit 命令)

sql

复制代码
-- 执行错误SQL后,输入edit编辑
edit

-- 修改完成后保存退出(记事本/vi),输入/重新执行
/

12.3 常见问题排查

12.3.1 连接失败
  • 检查OracleServiceORCLOracleTNSListener服务是否启动;
  • 检查用户名 / 密码是否正确(sys 需加as sysdba);
  • 检查监听配置(tnsnames.ora文件是否正确)。
12.3.2 权限不足
  • 普通用户(如 scott)无创建视图 / 索引权限,需 sys 授权: sql

    复制代码
    grant create view, create index to scott;
12.3.3 约束冲突
  • 外键约束:删除 / 修改主表数据时,需先处理子表关联数据(或用级联触发器);
  • 唯一约束:确保插入 / 更新的列值不重复;
  • 非空约束:确保必填列有值。
12.3.4 游标未关闭
  • 显式游标需手动关闭(或用 for 循环自动关闭),否则会占用内存;
  • 检查游标是否在异常处理中也关闭(避免异常导致未关闭)。
相关推荐
Chloeis Syntax1 小时前
MySQL初阶学习日记(3)--- 增查改删(CRUD)
数据库·学习·mysql
g***96901 小时前
MySQL版本选择与安装
数据库·mysql
c***72741 小时前
MySQL查看日志
数据库·mysql
222you1 小时前
MybatisPlus配置多数据源
数据库
321茄子1 小时前
MySQL 事务隔离性及锁
java·数据库·mysql
w***95491 小时前
MySQL无法连接到本地localhost的解决办法2024.11.8
数据库·mysql·adb
z***3351 小时前
PON架构(全光网络)
网络·数据库·架构
n***78681 小时前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
f***S2441 小时前
MyBatis-Plus 自定义 SQL 和复杂查询
数据库·sql·mybatis