使用PL/SQl
PL/SQl简介
PL/SQL 是过程语言(Procedural Language)与结构化查询语言(SQL)结合而成的编程语言
PL/SQL 是对 SQL 的扩展
支持多种数据类型,如大对象和集合类型,可使用条件和循环等控制结构
可用于创建存储过程、触发器和程序包,给SQL语句的执行添加程序逻辑
与 Oracle 服务器和 Oracle 工具紧密集成,具备可移植性、灵活性和安全性
PL/SQl优点
● 支持 SQL,在 PL/SQL 中可以使用:
(1)数据操纵命令
(2)事务控制命令
(2)游标控制
(4)SQL 函数和 SQL 运算符
● 支持面向对象编程 (OOP)
● 可移植性,可运行在任何操作系统和平台上的Oralce 数据库
● 更佳的性能,PL/SQL 经过编译执行
● 与 SQL 紧密集成,简化数据处理。
(1)支持所有 SQL 数据类型
(2)支持 NULL 值
(3)支持 %TYPE 和 %ROWTYPE 属性类型
● 安全性,可以通过存储过程限制用户对数据的访问
PL/SQl块
PL/SQL 分为三个部分,声明部分、可执行部分和异常处理部分
DECLARE
BEGIN
END;
变量常量
给变量赋值有两种方法:
使用赋值语句 :=
使用 SELECT INTO 语句
常量在列名后加constant
declare
-- 声明四个变量,初始化值
name varchar2(20) := '张三';
sex varchar2(10);
-- 声明常量
age constant number :=20;
now date := sysdate;
begin
sex :='男';
-- sge :=21;常量不能修改
-- 使用select into 赋值
select s.name into name from student s where s.id = 2;
dbms_output.put_line('姓名:'||name||'性别:'||sex||'年龄:'||age||'时间:'||now);
end;
数据类型
标量类型
数字
字符
字符数据类型包括:
char
varchar2
long
raw
long raw
布尔型
此类别只有一种类型,即BOOLEAN类型
用于存储逻辑值(TRUE、FALSE和NULL)
不能向数据库中插入BOOLEAN数据
不能将列值保存到BOOLEAN变量中
只能对BOOLEAN变量执行逻辑操作
日期时间
存储日期和时间数据
常用的两种日期时间类型
DATE
TIMESTAMP
LOB类型
属性类型
用于引用数据库列的数据类型,以及表示表中一行的记录类型
属性类型有两种:
%TYPE - 引用变量和数据库列的数据类型
%ROWTYPE - 提供表示表中一行的记录类型
使用属性类型的优点:
不需要知道被引用的表列的具体类型
如果被引用对象的数据类型发生改变,PL/SQL 变量的数据类型也随之改变
逻辑比较
逻辑比较用于比较变量和常量的值,这些表达式称为布尔表达式
布尔表达式由关系运算符与变量或常量组成
布尔表达式的结果为TRUE、FALSE或NULL,通常由逻辑运算符AND、OR和NOT连接
控制结构
条件控制
IF 语句
--输入一个成绩,输出成绩等级
declare
score number;
begin
-- 接收键盘输入内容
score := &请输入成绩;
if score >=90 then
dbms_output.put_line('优秀');
elsif score >=80 then
dbms_output.put_line('良好');
elsif score >=70 then
dbms_output.put_line('中等');
elsif score >=60 then
dbms_output.put_line('及格');
else
dbms_output.put_line('不及格');
end if;
end;
CASE 语句
-- 输入成绩等级,输出对应的内容
declare
score varchar2(4) := '&输入成绩等级(A、B、C、D、E)';
begin
case score
when 'A' then
dbms_output.put_line('优秀');
when 'B' then
dbms_output.put_line('良好') ;
when 'C' then
dbms_output.put_line('中等');
when 'D' then
dbms_output.put_line('及格');
when 'E' then
dbms_output.put_line('不及格');
else
dbms_output.put_line('输入错误!');
end case;
end;
循环控制
LOOP 循环
-- loop 计算1~100 的和
declare
he number :=0;
i number :=1;
begin
loop
he := he + i;
exit when i = 100;
i := i + 1;
end loop;
dbms_output.put_line('总和为:'||he);
end;
WHILE 循环
-- while 计算1~100 的和
declare
he number :=0;
i number :=1;
begin
--只有符合条件才进入loop循环体
while i<=100
loop
he := he + i;
i := i + 1;
end loop;
dbms_output.put_line('总和为:'||he);
end;
FOR 循环
-- for 计算1~100 的和
declare
he number := 0;
begin
--i 的取值从1~100 reverse 循环从后面开始
for i in reverse 1..100
loop
dbms_output.put_line(i);
he := he + i;
end loop;
dbms_output.put_line(he);
end;
顺序控制
GOTO 语句
NULL 语句
异常处理机制
● 在运行程序时出现的错误叫做异常
● 发生异常后,语句将停止执行,控制权转移到 PL/SQL 块的异常处理部分
● 异常有两种类型:
(1)预定义异常 - 当 PL/SQL 程序违反 Oracle 规则或超越系统限制时隐式引发
(2)用户定义异常 - 用户可以在 PL/SQL 块的声明部分定义异常,自定义的异常通过 RAISE 语句显式引发
-- 系统内置异常使用
declare
i number :=10;
begin
dbms_output.put_line('i/0='||(i/0));
-- 异常处理部分
exception
-- 根据不同的异常类型进行处理,zero_divide 除数为0 异常处理
when zero_divide then
dbms_output.put_line('除数不能为0');
when others then
dbms_output.put_line('其他异常');
end;
-- 用户自定义异常使用
declare
-- 自定义异常,用关键字 exception 声明
sex_exception exception;
sex varchar2(10);
age_exception exception;
age number;
begin
sex := '&请输入性别:';
age := &请输入年龄:;
if sex not in('男','女') then
-- 抛出自定义异常
raise sex_exception;
end if;
if age not between 0 and 150 then
raise age_exception;
end if;
exception
-- 捕获处理自定义异常
when sex_exception then
-- dbms_output.put_line('性别只能为男或者女');
-- 弹出自定义错误信息窗口,第一个参数是错误代码,取值范围是-20001~-20999,第二个参数是错误信息描述,最大长度2048字节
raise_application_error(-20001,'性别只能为男或者女');
when age_exception then
-- dbms_output.put_line('年龄只能输入0~150之间');
raise_application_error(-20002,'年龄只能输入0~150之间');
end;
常见异常类型
CURSOR_ALREADY_OPEN 试图"OPEN"一个已经打开的游标
DUP_VAL_ON_INDEX 试图向有"UNIQUE"中插入重复的值
INVALID_CURSOR 试图对以关闭的游标进行操作
INVALID_NUMBER 在SQL语句中将字符转换成数字失败
LOGIN_DENIED 使用无效用户登陆
NO_DATA_FOUND 没有找到数据时
TIMEOUT_ON_RESOURCE Oracle等待资源期间发生超时
TOO_MANY_ROWS "SELECT INTO"返回多行时
VALUE_ERROR 当出现赋值错误
ZERO_DIVIDE 除数为零
总结
PL/SQL 是一种可移植的高性能事务处理语言
PL/SQL 块由声明部分、可执行部分和异常处理部分组成
PL/SQL 数据类型包括标量数据类型
控制结构包括条件控制、循环控制和顺序控制
PL/SQL 支持动态 SQL
运行时出现的错误叫做异常
异常可以分为预定义异常和用户定义的异常
游标
逐行处理查询结果,以编程的方式访问数据
游标的类型:
隐式游标、显式游标、REF游标
隐式游标
● 在PL/SQL中使用DML语句时自动创建隐式游标
● 隐式游标自动声明、打开和关闭,其名为 SQL
● 通过检查隐式游标的属性可以获得最近执行的DML 语句的信息
● 隐式游标的属性有:
(1)%FOUND – SQL 语句影响了一行或多行时为 TRUE
(2)%NOTFOUND – SQL 语句没有影响任何行时为TRUE
(3)%ROWCOUNT – SQL 语句影响的行数
(4)%ISOPEN - 游标是否打开,始终为FALSE
-- 隐式游标常用属性使用,隐式游标固定名称sql,在执行dml语句时自动创建,打开,关闭
declare
begin
update emp e set e.salary = e.salary + &新加工资 where e.employee_id = &员工编号;
-- 影响到数据行返回true ,不影响返回false 和 sql%notfound相反
if sql%found then
--sql%rowcount影响到的行数
dbms_output.put_line('成功更新'||sql%rowcount||'行数据');
end if;
commit;
-- sql%isopen 判断游标是否关闭,永远是false
if sql%isopen then
dbms_output.put_line('游标状态打开');
else
dbms_output.put_line('游标状态关闭');
end if;
end;
显式游标
显式游标在 PL/SQL 块的声明部分定义查询,该查询可以返回多行
-- 使用显示游标遍历表所有记录
declare
student_row student%rowtype;
--使用关键字cursor声明游标变量student_cursor并初始化值
cursor student_cursor is select * from student order by id;
begin
-- 打开游标
open student_cursor;
loop
-- 逐行读取
fetch student_cursor into student_row;
--游标中没有数据退出循环
exit when student_cursor%notfound;
dbms_output.put_line('编号:'||student_row.id||'姓名:'||student_row.name);
end loop;
-- 关闭游标
close student_cursor;
end;
-- 带参数的显示游标,根据部门编号查询员工信息
declare
emp_row emp%rowtype;
-- 声明带参数的游标,多个参数逗号隔开
cursor emp_cursor(dept_no emp.department_id%type)is select e.* from emp e where e.department_id = dept_no;
begin
--打开游标,传递对应的参数值
open emp_cursor(&部门编号);
loop
fetch emp_cursor into emp_row;
exit when emp_cursor%notfound;
dbms_output.put_line('员工编号:'||emp_row.employee_id||' 工资:'||emp_row.salary);
end loop;
close emp_cursor;
end;
循环游标
-- 循环游标=显示游标+for循环
declare
emp_row emp%rowtype;
cursor emp_cursor is select * from emp;
begin
for emp_row in emp_cursor
loop
dbms_output.put_line('员工编号:'||emp_row.employee_id||' 工资:'||emp_row.salary);
end loop;
end;
REF游标
REF 游标用于处理运行时才能确定的动态 SQL 查询的结果
REF 游标和游标变量 **
REF 游标和游标变量**用于处理运行时动态执行的 SQL 查询
创建游标变量需要两个步骤:
声明 REF 游标类型
声明 REF 游标类型的变量
用于声明 REF 游标类型的语法为:
TYPE <ref_cursor_name> IS REF CURSOR
[RETURN <return_type>];
-- 创建REF游标
declare
-- 声明ref游标类型emp_ref_cursor,指定ret游标返回类型是emp表行类型
type emp_ref_cursor is ref cursor return emp%rowtype;
-- 使用emp_ref_cuesor类型声明游标变量emp_cursor
emp_cursor emp_ref_cursor;
emp_row emp%rowtype;
begin
--打开游标并初始化值
open emp_cursor for select e.* from emp e;
loop
fetch emp_cursor into emp_row;
exit when emp_cursor%notfound;
dbms_output.put_line('员工编号:'||emp_row.employee_id||' 工资:'||emp_row.salary);
end loop;
close emp_cursor;
end;
-- 使用ref游标执行动态sql
declare
-- 声明ref游标类型emp_ref_cursor,执行动态sql游标不能有返回值
type emp_ref_cursor is ref cursor;
-- 使用emp_ref_cuesor类型声明游标变量emp_cursor
emp_cursor emp_ref_cursor;
emp_row emp%rowtype;
begin
--打开游标并初始化值,使用动态sql赋值,动态sql用单引号括起来,参数用占位符,using 赋值
open emp_cursor for 'select e.* from emp e where e.department_id = :1' using &部门编号;
loop
fetch emp_cursor into emp_row;
exit when emp_cursor%notfound;
dbms_output.put_line('员工编号:'||emp_row.employee_id||' 工资:'||emp_row.salary);
end loop;
close emp_cursor;
end;
总结
游标作用:用于处理查询结果集中的数据
游标类型有:隐式游标、显式游标和 REF 游标
隐式游标由 PL/SQL 自动定义、打开和关闭
显式游标用于处理返回多行的查询
显式游标可以删除和更新活动集中的行
要处理结果集中所有记录时,可使用循环游标
在声明 REF 游标时,不需要将 SELECT 语句与 其关联
子程序和程序包
子程序
● 命名的 PL/SQL 块,编译并存储在数据库中。
● 子程序的各个部分:
(1)声明部分
(2)可执行部分
(3)异常处理部分(可选)
● 子程序的分类:
(1)过程 - 执行某些操作
(2)函数 - 执行操作并返回值
● 子程序的优点:
(1)模块化
将程序分解为逻辑模块
(2)可重用性
可以被任意数目的程序调用
(3)可维护性
简化维护操作
(4)安全性
通过设置权限,使数据更安全
过程 procedure
过程参数的三种模式:
IN
用于接受调用程序的值
默认的参数模式
OUT
用于向调用程序返回值
IN OUT
用于接受调用程序的值,并向调用程序返回更新的值
-- 创建存储过程,根据员工编号输出员工信息
create or replace procedure find_emp_no(emp_no emp.employee_id%type)
is
emp_row emp%rowtype;
begin
select e.* into emp_row from emp e where e.employee_id = emp_no;
dbms_output.put_line('编号'||emp_row.employee_id||'姓名'||emp_row.salary);
end;
-- 调用存储过程
-- 方式一,使用pl/sql程序调用
declare
begin
-- 过程名称和参数调用
find_emp_no(&员工编号);
end;
-- 方式二,在命令行窗口执行execute find_emp_no(100);如果控制台不显示输出结果,执行set serveroutput on
-- 创建存储过程,根据员工编号获取薪资,参数模式in默认的,用来调用者传入,out用来传出去给调用过程者使用
create or replace procedure get_salary_by_empno(emp_no in emp.employee_id%type,salary out emp.salary%type)
is
begin
select e.salary into salary from emp e where e.employee_id = emp_no;
end;
-- 调用
declare
salary emp.salary%type;
begin
dbms_output.put_line('工资'||salary);
get_salary_by_empno(&员工编号,salary);
dbms_output.put_line('工资'||salary);
end;
函数 function
-- 创建函数,根据员工编号返回员工工资
create or replace function get_salary_by_emp_no(emp_no emp.employee_id%type) return emp.salary%type
is
salary emp.salary%type;
begin
select e.salary into salary from emp e where e.employee_id = emp_no;
return salary;-- ruturn 返回一个值,类型必须跟声明时一致
end;
--函数调用
-- 方式一,pl/sql程序块调用
declare
salary emp.salary%type;
begin
salary := get_salary_by_emp_no(&员工编号);
dbms_output.put_line('工资'||salary);
end;
-- 方式二,使用dual伪表
select get_salary_by_emp_no(101) from dual;
-- 创建函数,实现根据部门编号返回总人数total_by_dept_no
create or replace function total_by_dept_no(emp_date emp.department_id%type) return number
is
peonumber number;
begin
select count(e.employee_id) into peonumber from emp e where e.department_id = emp_date;
return peonumber;
end;
-- 调用
declare
peonumber number;
begin
peonumber := total_by_dept_no(&输入部门);
dbms_output.put_line('部门人数'||peonumber);
end;
select total_by_dept_no(50) from dual;
函数和过程的联系与区别
相同点:
都是子程序,封装pl/sql语句块,可以接收传递参数,拥有封装、模块化、可复用性、安全性作用,都可以使用pl/sql程序块调用
不同点:
函数有返回值,过程参数有三种模式,过程本身不返回值,但是可以通过参数out模式回写多个值,函数使用select 函数名 from dual 调用,过程在命令窗口使用execute 过程名 调用
程序包
程序包是对相关过程、函数、变量、游标和异常等对象的封装
程序包由规范和主体两部分组成
程序包的优点
模块化
更轻松的应用程序设计
信息隐藏
新增功能
性能更佳
程序包中的游标
游标的定义分为游标规范和游标主体两部分
在包规范中声明游标规范时必须使用 RETURN 子句指定游标的返回类型
RETURN子句指定的数据类型可以是:
用 %ROWTYPE 属性引用表定义的记录类型
程序员定义的记录类型
-- 创建程序包规范
create or replace package my_pack
as
procedure find_emp_no(emp_no emp.employee_id%type);
procedure get_salary_by_empno(emp_no in emp.employee_id%type,salary out emp.salary%type);
procedure swap(number_one in out number,number_two in out number);
function get_salary_by_emp_no(emp_no emp.employee_id%type) return emp.salary%type;
function total_by_dept_no(emp_date emp.department_id%type) return number;
function total_sum(a number,b number) return number;
end my_pack;
-- 创建程序包主体
create or replace package body my_pack
as
--------------------------find_emp_no开始---------------------------------------
procedure find_emp_no(emp_no emp.employee_id%type)
is
emp_row emp%rowtype;
begin
select e.* into emp_row from emp e where e.employee_id = emp_no;
dbms_output.put_line('编号'||emp_row.employee_id||'姓名'||emp_row.salary);
end find_emp_no;
---------------------------get_salary_by_empno开始-----------------------------
procedure get_salary_by_empno(emp_no in emp.employee_id%type,salary out emp.salary%type)
is
begin
select e.salary into salary from emp e where e.employee_id = emp_no;
end get_salary_by_empno;
----------------------------swap-------------------------
procedure swap(number_one in out number,number_two in out number)
is
swap_num number;
begin
swap_num := number_one;
number_one := number_two;
number_two := swap_num;
end swap;
-----------------------------get_salary_by_emp_no-----------
function get_salary_by_emp_no(emp_no emp.employee_id%type) return emp.salary%type
is
salary emp.salary%type;
begin
select e.salary into salary from emp e where e.employee_id = emp_no;
return salary;-- ruturn 返回一个值,类型必须跟声明时一致
end get_salary_by_emp_no;
------------------------------total_by_dept_no----------
function total_by_dept_no(emp_date emp.department_id%type) return number
is
peonumber number;
begin
select count(e.employee_id) into peonumber from emp e where e.department_id = emp_date;
return peonumber;
end total_by_dept_no;
-------------------------------total_sum-------------------
function total_sum(a number,b number) return number
is
tosum number :=0;
begin
if a<b then
for i in a..b
loop
tosum := tosum+i;
end loop;
else
for i in b..a
loop
tosum := tosum+i;
end loop;
end if;
return tosum;
end total_sum;
------------------------------------------------------------------
end my_pack;
-- 程序包调用
declare
salary emp.salary%type;
num1 number := 100;
num2 number := 200;
total number;
begin
my_pack.find_emp_no(100);
my_pack.get_salary_by_empno(100,salary);
dbms_output.put_line('工资'||salary);
dbms_output.put_line('num1:'||num1||',num2:'||num2);
my_pack.swap(num1,num2);
dbms_output.put_line('num1:'||num1||',num2:'||num2);
dbms_output.put_line('工资:'||my_pack.get_salary_by_emp_no(100));
dbms_output.put_line('总人数:'||my_pack.total_by_dept_no(90));
dbms_output.put_line('总和:'||my_pack.total_sum(1,100));
end;
-- 创建程序包规范,输入部门编号输出所有员工
create or replace package emp_pack
as
-- 声明一个游标,指定返回值类型,即游标中存放的数据类型
cursor emp_cursor(deptno emp.department_id%type) return emp%rowtype;
-- 根据部门编号输出所有员工信息
procedure get_emps_by_deptno(deptno emp.department_id%type);
-- 根据部门编号获取总员工数
function total(deptno emp.department_id%type) return number;
end emp_pack;
-- 创建程序包主体
create or replace package body emp_pack
as
cursor emp_cursor(deptno emp.department_id%type) return emp%rowtype
is select e.* from emp e where e.department_id=deptno;
-------------------------------------------------------------------
procedure get_emps_by_deptno(deptno emp.department_id%type)
is
emp_row emp%rowtype;
begin
-- 打开游标
open emp_cursor(deptno);
loop
fetch emp_cursor into emp_row;
exit when emp_cursor%notfound;
dbms_output.put_line('编号:'||emp_row.employee_id||'工资:'||emp_row.salary);
end loop;
close emp_cursor;
end get_emps_by_deptno;
-----------------------------------------------------------------------
function total(deptno emp.department_id%type) return number
is
total_num number;
begin
select count(*) into total_num from emp e where e.department_id = deptno;
return total_num;
end total;
end emp_pack;
--调用
declare
dept emp.department_id%type := &输入部门;
begin
dbms_output.put_line(dept||'部门人数总和:'||emp_pack.total(90));
dbms_output.put_line('成员如下');
emp_pack.get_emps_by_deptno(90);
end;
总结
子程序是命名的 PL/SQL 块,可带参数并可在需要时随时调用
有两种类型的PL/SQL子程序,即过程和函数
过程用户执行特定的任务,函数用于执行任务并返回值
程序包是对相关类型、变量、常量、游标、异常、过程和函数等对象的封装
程序包由两部分组成,即包规范和包主体
使用程序包的优点是:模块化、更轻松的程序设计、信息隐藏、新增功能以及性能更佳
触发器
● 触发器是当特定事件出现时自动执行的存储过程
● 特定事件可以是执行更新的DML语句和DDL语句
● 触发器不能被显式调用
● 触发器的功能:
(1)自动生成数据
(2)自定义复杂的安全权限
(3)提供审计和日志记录
(4)启用复杂的业务逻辑
创建触发器的语法
CREATE [OR REPLACE] TRIGGER trigger_name
AFTER | BEFORE | INSTEAD OF
[INSERT] [[OR] UPDATE [OF column_list]]
[[OR] DELETE]
ON table_or_view_name
[REFERENCING {OLD [AS] old / NEW [AS] new}]
[FOR EACH ROW]
[WHEN (condition)]
pl/sql_block;
触发器的组成部分
触发器由三部分组成:
(1)触发器语句(事件)
定义激活触发器的 DML 事件和 DDL 事件
(2)触发器限制
执行触发器的条件,该条件必须为真才能激活触发器
(3)触发器操作(主体)
包含一些 SQL 语句和代码,它们在发出了触发器语句且触发限制的值为真时运行
-- 创建触发器,在删除学生记录之后触发
create or replace trigger student_trigger
after -- 后置触发器
delete --删除操作
on student --作用的表名称
for each row --行级触发器,影响一行触发一次,如果不写,语句级触发器,执行一条语句不管影响多少行只触发一次
--pl/sql语句块
begin
-- :old 指旧的记录行对象,获取该行的所有字段值,:new值新的记录行对象
dbms_output.put_line('学生记录【'||:old.id||'】被删除');
end;
select * from student order by id;
delete from student where id <= 5;
commit;
-- 创建触发器,对学生记录新增、修改、删除操作之后进行触发
create or replace trigger stu_tirgger.
after
insert or update or delete
on student
for each row
begin
-- 新增操作
if inserting then
dbms_output.put_line(sysdate||'学生记录【'||:new.name||'】新增成功');
--修改操作
elsif updating then
dbms_output.put_line(sysdate||'学生记录【'||:old.id||'】密码由【'||:old.password||'】改成新密码【'||:new.password||'】');
-- 删除操作
elsif deleting then
dbms_output.put_line(sysdate||'学生记录【'||:old.id||'】被删除');
end if;
end;
-- 创建触发器,只能在12点~13点之间新增、修改、删除表
create or replace trigger stu_trigger1
before
insert or delete or update
on student
for each row
begin
if to_char(sysdate,'hh24')!=12 then
raise_application_error('-20001','学生记录只能在12点~13点之间进行操作');
end if;
end;
--禁用触发器
alter trigger stu_trigger1 disable;
-- 启用触发器
alter trigger stu_trigger1 enable;
-- 删除触发器
drop trigger stu_trigger1;
--创建instead of 触发器,对视图数据进行修改,针对于包含来自包含多个表的字段的视图数据修改
create or replace trigger view_emp_trigger
instead of
insert or update or delete
on view_emp
for each row
begin
if inserting then
dbms_output.put_line('新增操作');
elsif updating then
update dept d set d.department_name = :new.dept_name where d.department_id = (select e.department_id from emp e where e.employee_id = :old.id);
elsif deleting then
dbms_output.put_line('删除操作');
end if;
end;
-- 模式触发器,用户级别,主要用在ddl 语句中,创建、修改、删除对象时触发
create or replace trigger obj_trigger
after -- 后置触发器
drop or alter or create -- 删除对象
on schema -- 作用在模式上,当前用户
begin
dbms_output.put_line('当前对象:'||ora_dict_obj_owner||'对象名称:'||ora_dict_obj_name||',对象类型:'||ora_dict_obj_type||',操作时间:'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'));
end;
drop table student_bak20210806;
create table test(
id number,
name varchar2(10)
);
create index idx_test_name on test(name);
drop index idx_test_name;
drop table test;
总结
触发器是当特定事件出现时自动执行的存储过程
触发器分为 DML 触发器、DDL 触发器和数据库级触发器三种类型
DML 触发器的三种类型包括行级触发器、语句级触发器和 INSTEAD OF 触发器