PL/SQL (Procedure Language/SQL):对SQL 语言过程化的一个扩展,比如,在SQL 语言中添加逻辑分支、循环等,使SQL 语言具有过程化处理能力;是面向过程的编程语言。
1. 结构
DECLARE
-- 声明部分:变量、类型、游标、局部存储过程和函数
BEGIN
-- 执行部分
EXCEPTION
-- 异常处理部分
End;
其中【 BEGIN ~ End; 】部分是必须的。
2. Hello World 输出
BEGIN
-- 输出语句
DBMS_OUTPUT.PUT_line('Hello world');
END PROCEDURE_HELLO;
执行后输出:Hello world
也可在sqlplus 中执行:
C:\Users\lenovo>sqlplus
请输入用户名: system as sysdba
输入口令:
SQL> BEGIN
2 -- 输出语句
3 DBMS_OUTPUT.PUT_line('Hello world');
4
5 END PROCEDURE_HELLO;
6 /
PL/SQL 过程已成功完成。
SQL> set serveroutput on
SQL> BEGIN
2 -- 输出语句
3 DBMS_OUTPUT.PUT_line('Hello world');
4
5 END PROCEDURE_HELLO;
6 /
Hello world
PL/SQL 过程已成功完成。
注意:
在SQLPLUS 中执行时,结尾处放一个【/】,表示过程结束;
且默认输出是关闭状态,需要开启,命令:set serveroutput on
3. 命名规则
标识符 | 命名规则 | 例子 |
程序变量 | V_name | V_name |
程序常量 | V_name | V_person_name |
游标变量 | Name_cursor | Per_cursor |
异常标识 | E_name | E_too_many |
表类型 | Name_table_type | Per_record_type |
表 | Name_table | Emp |
记录类型 | Name_record | Emp_record |
SQL/Plus 替代变量 | P_name | P_sal |
绑定变量 | G_name | G_year_sal |
4. 基本类型
声明:变量名 变量类型
v_name varchar2(20)
赋值:
直接赋值:v_name := ‘chengyu’
语句赋值: select … into := 变量
4.1 自定义变量类型
set serveroutput on;
DECLARE
v_name VARCHAR2(20) := 'chengyu';
v_sallary NUMBER;
v_address VARCHAR2(200);
v_birthday date:=to_date('2021-08-26','YYYY-MM-DD');
BEGIN
-- 直接赋值
v_sallary := 500;
-- 语句赋值
select '大连' into v_address from dual;
DBMS_OUTPUT.PUT_LINE('v_name='||v_name||',v_sallary='||v_sallary||',v_address'||v_address);
End;
输出:v_name=chengyu,v_sallary=500,v_address大连
4.2 引用型变量类型
引用型变量的类型与长度,取决于表中字段的类型和长度;
语法:表名.列名%TYPE,如 v_name user_tab.username%TYPE
DECLARE
v_name user_tab.username%TYPE;
v_age user_tab.age%TYPE;
BEGIN
select username,age into v_name,v_age from user_tab where uuid = '1000';
DBMS_OUTPUT.PUT_LINE('v_name='||v_name||',v_age='||v_age);
End;
输出:v_name=chengyu,v_age=30
5. 复合类型
5.1 数组类型变量 varray
5.1.1 一维数组
declare
type user_varray is varray(2) of varchar2(20);
v_users user_varray:=user_varray('yongyan','yongbin');
begin
for x in v_users.first .. v_users.last loop
dbms_output.put_line(v_users(x));
end loop;
end;
5.1.2 二维数组
declare
type user_varray is varray(2) of varchar2(20);
v_user_1 user_varray:=user_varray('yongyan','yongbin');
v_user_2 user_varray:=user_varray('chengyu','chenglong');
type users_varray is varray(2) of user_varray;
v_users users_varray:=users_varray(v_user_1,v_user_2);
begin
for x in v_users.first .. v_users.last loop
for y in v_users(x).first .. v_users(x).last loop
dbms_output.put_line(v_users(x)(y));
end loop;
end loop;
end;
输出:
yongyan
yongbin
chengyu
chenglong
5.2 记录类型
5.2.1 自定义一行 record
declare
-- 声明记录类型
type person_record is record(
p_name varchar(20) not null default 'chengyu',
p_age number(7),
birthday date:=to_date('2021-08-26','YYYY-MM-DD')
);
-- 定义记录类型变量
v_dalian_persons person_record;
begin
--赋值
v_dalian_persons.p_name:='chenglong';
v_dalian_persons.p_age:=30;
v_dalian_persons.birthday:=to_date('1990-01-01','YYYY-MM-DD');
-- select name,age,birthday into v_dalian_persons from person_t where id = '1001'
--输出
dbms_output.put_line('dalian_persons p_name : '|| v_dalian_persons.p_name);
dbms_output.put_line('dalian_persons p_age : '|| v_dalian_persons.p_age);
dbms_output.put_line('dalian_persons birthday : '|| v_dalian_persons.birthday);
end;
输出:
dalian_persons p_name : chenglong
dalian_persons p_age : 30
dalian_persons birthday : 01-1月 -90
5.2.2 表中一行 rowtype
语法:变量名称 表名%ROWTYPE,如 v_user username%rowtype;
DECLARE
-- 记录类型变量
v_user user_tab%ROWTYPE;
BEGIN
select * into v_user from user_tab where uuid = '1000';
DBMS_OUTPUT.PUT_LINE('v_name='||v_user.USERNAME||',v_age='||v_user.AGE||',v_address='||v_user.ADDRESS);
End;
输出:v_name=chengyu,v_age=30,v_address=大连
5.3 表类型变量 table
相当于java中的Map容器!或一个可变长的数组!
语法如下:
type 表类型 is table of 类型 index by binary_integer;
表变量名 表类型;
index by binary_integer 子句代表以符号整数为索引!
值为标量,也可以是record类型!
5.3.1 Table 存基本类型(带角标一维数组)
declare
type t_tb is table of varchar2(20) index by binary_integer;
v_tb t_tb;
begin
v_tb(100):='hello';
v_tb(98):='world';
dbms_output.put_line(v_tb(100));
dbms_output.put_line(v_tb(98));
end;
5.3.2 Table 存 Record(多维数组)
declare
type t_rd is record(id number,name varchar2(20));
type t_tb is table of t_rd index by binary_integer;
v_tb2 t_tb;
begin
v_tb2(100).id:=1;
v_tb2(100).name:='hello';
dbms_output.put_line(v_tb2(100).id);
dbms_output.put_line(v_tb2(100).name);
end;
DECLARE
type person_record is record(
username varchar(20),
age number(7)
);
dalian_persons person_record;
TYPE rec_table_type IS TABLE OF person_record INDEX BY BINARY_INTEGER;
rec_table rec_table_type;
BEGIN
FOR i IN 1000 .. 1003
LOOP
SELECT username,age INTO rec_table(i) FROM user_tab WHERE uuid = i;
--dbms_output.put_line(i);
END LOOP;
FOR i IN rec_table.first .. rec_table.last
LOOP
dbms_output.put_line(rec_table(i).username ||' '||rec_table(i).age);
END LOOP;
END;
user_tab:
1000 chengyu 30 26-8月 -21 大连
1001 chenglong 40 26-8月 -21 香港
1002 chengwei 20 26-8月 -21 大连
1003 chengyu 20 26-8月 -21 日本
输出:
chengyu 30
chenglong 40
chengwei 20
chengyu 20
5.3.3 Table 存 rowtype
相当于多维数组!
declare
type menutype is table of menu%rowtype index by binary_integer;
menus menutype;
begin
select * into menus(2) from menu where id=1;
dbms_output.put_line(menus(2).group_name||'='||menus(2).menu_name);
end;
5.4 复合类型的属性和方法
- first:取集合第一个元素的下标
- last:取集合最后一个元素的下标
- count:取集合中存放的元素个数(实际存放数据的个数)
- limit:取集合能存放的数据个数(集合的长度,用于变长数组,索引表和嵌套表是无限长度)
- next(下标):取当前下标的下一个元素的下标
- prior(下标):取当前下标的上一个元素的下标
- extend(n,index):表示将集合扩展n个元素,index表示集合已有元素的下标,如果有第二个参数,表示扩展集合n个元素并且扩展后的对应值是index这个下标所对应的值
- delete(n):删除集合中的元素
6. 逻辑控制
6.1 条件分支
语法:
IF 条件1 THEN 执行1
ELSIF 条件2 THEN 执行2
ELSE 执行3
END IF;
DECLARE
v_age user_tab.age%TYPE;
BEGIN
select age into v_age from user_tab where uuid = '1000';
IF v_age > 30 THEN
DBMS_OUTPUT.PUT_LINE('不年轻啦!');
ELSIF v_age > 10 THEN
DBMS_OUTPUT.PUT_LINE('风华正茂!');
ELSE
DBMS_OUTPUT.PUT_LINE('小屁孩!');
END IF;
End;
输出:风华正茂!
6.2 流程控制
循环:
语法:
LOOP
EXIT WHEN 退出循环
END LOOP;
DECLARE
v_num number := 1;
BEGIN
Loop
EXIT WHEN v_num > 10;
DBMS_OUTPUT.PUT_LINE(v_num);
v_num := v_num + 1;
END LOOP;
End;
输出1~10
7. 游标
用于临时存储一个查询返回的多行数据(类似于Java ResultSet集合),通过遍历游标,可以逐行访问处理结果;
7.1 使用方式
声明 ⇒ 打开 ⇒ 读取 ⇒ 关闭
声明:CURSOR 游标名(参数列表) IS 查询语句;
打开:OPEN 游标名;
读取:FETCH 游标名 INTO 变量列表;
关闭:CLOSE 游标名;
7.2 游标属性
属性 | 返回值类型 | 说明 |
%ROWCOUNT | 整型 | 返回数据行数 |
%FOUND | 布尔型 | 读取(FETCH )数据,返回一行为真,否则为假 |
%NOTFOUND | 布尔型 | 与%FOUND 相反,常用于退出循环 |
%ISOPEN | 布尔型 | 游标已打开为真,否则为假 |
7.3 例子(无参)
遍历人员表中人员名称及年龄
DECLARE
-- 声明游标并赋值
CURSOR c_users IS SELECT username,age FROM user_tab;
-- 声明接受游标元素的变量
v_name user_tab.username%TYPE;
v_age user_tab.age%TYPE;
BEGIN
-- 打开游标
OPEN c_users;
-- 遍历游标
Loop
-- 获取游标中数据(注意顺序与表中一致)
FETCH c_users INTO v_name,v_age;
-- 退出循环条件
EXIT WHEN c_users%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('v_name='||v_name||',v_age='||v_age);
END LOOP;
-- 关闭游标
CLOSE c_users;
End;
输出:
v_name=chengyu,v_age=30
v_name=chenglong,v_age=40
v_name=chengwei,v_age=20
7.4 例子(有参)
遍历人员表中年龄大于25岁的人员名称及年龄
DECLARE
-- 声明游标(设置形参)
CURSOR c_users(v_age user_tab.age%TYPE) IS SELECT username,age FROM user_tab where age > v_age;
-- 声明接受游标元素的变量
v_name user_tab.username%TYPE;
v_age user_tab.age%TYPE;
BEGIN
-- 打开游标(传入实参)
OPEN c_users(25);
-- 遍历游标
Loop
-- 获取游标中数据(注意顺序与表中一致)
FETCH c_users INTO v_name,v_age;
-- 退出循环条件
EXIT WHEN c_users%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('v_name='||v_name||',v_age='||v_age);
END LOOP;
-- 关闭游标
CLOSE c_users;
End;
输出:
v_name=chengyu,v_age=30
v_name=chenglong,v_age=40
8. 异常处理
8.1 语法
Exception
WHEN exception1 [Or exception2...] THEN
statement1;
WHEN exception1 [Or exception2...] THEN
statement2;
WHEN OTHERS THEN
statement3;
8.2 预定义异常
Oracle 已经定义好的异常,直接通过异常名称进行捕获
① NO_DATA_FOUNT:执行的sql 语句没有查询到结果
② TOO_MANY_ROWS:数据太多
③ INVALID_CURSOR:失效的游标(游标没打开等情况)
④ ZERO_DIVIDE:除数为 0
⑤ DUP_VAL_ON_INDEX:违反主键约束
⑥ VALUE_ERROR:赋值异常(类型不一致)
DECLARE
CURSOR c_users IS SELECT username,age FROM user_tab;
v_name user_tab.username%TYPE;
v_age user_tab.age%TYPE;
BEGIN
--没数据异常
--SELECT username INTO V_NAME from user_tab where uuid = '1005';
--数据太多异常
--SELECT username INTO V_NAME from user_tab where uuid = '1000' or uuid = '1001';
--失效游标异常
--FETCH c_users INTO v_name,v_age;
--除数为 0
--v_age := 1/0;
--违反主键约束异常
--INSERT INTO user_tab VALUES('1000','','','','');
--类型不一致
v_age := 'a';
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('没数据异常!');
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('数据太多异常!');
WHEN INVALID_CURSOR THEN
dbms_output.put_line('失效游标异常!');
WHEN ZERO_DIVIDE THEN
dbms_output.put_line('除数为 0异常!');
WHEN DUP_VAL_ON_INDEX THEN
dbms_output.put_line('违反主键约束异常!');
WHEN VALUE_ERROR THEN
dbms_output.put_line('类型不一致异常!');
WHEN OTHERS THEN
dbms_output.put_line('其他异常!');
END;
※自定义异常略
9. 存储过程
PL/SQL 语言做成的业务存储起来,便于重复调用。
开发过程中,某些特定的功能,会对数据库进行多次连接、关闭(耗资源),对数据进行多次的IO读写等操作,性能比较低,使用存储过程,可以做到连接关闭一次数据库就可以实现业务需求,可以大大提高效率。
9.1 语法
注意:DECLARE 关键字省略
CREATE OR REPLACE PROCEDURE 存储过程名
AS
BEGIN
NULL;
END PROCEDURE1;
可带参数,可有返回值
9.2 无参
过程 ⇒ 新建过程
CREATE OR REPLACE PROCEDURE P_HELLO AS
-- 声明变量
v_name varchar(20) := 'CHENGYU';
BEGIN
DBMS_OUTPUT.PUT_LINE('HELLO '||v_name);
END P_HELLO;
输出:HELLO CHENGYU
① SQL工作表中调用:
BEGIN
P_HELLO;
END;
② SQL PLUS 中调用
SQL> exec P_HELLO ;
HELLO CHENGYU
PL/SQL 过程已成功完成。
9.3 带输入参数
CREATE OR REPLACE PROCEDURE P_PARAMETER(i_id IN user_tab.uuid%TYPE) AS
v_name user_tab.username%TYPE;
v_age user_tab.age%TYPE;
BEGIN
select username,age into v_name,v_age from user_tab where uuid = i_id;
DBMS_OUTPUT.PUT_LINE('v_name='||v_name||',v_age='||v_age);
END P_PARAMETER;
① SQL工作表中调用:
BEGIN
P_PARAMETER(1001);
END;
输出:v_name=chenglong,v_age=40
② SQL PLUS 中调用
SQL> exec P_PARAMETER(1000);
v_name=chengyu,v_age=30
PL/SQL 过程已成功完成。
9.4 带输出参数(有返回值)
一般提供给第三方程序调用;
1)定义存储过程
create or replace PROCEDURE P_PARAMETER_OUT(i_id IN user_tab.uuid%TYPE,o_name OUT user_tab.username%TYPE,o_age OUT user_tab.age%TYPE) AS
BEGIN
select username,age into o_name,o_age from user_tab where uuid = i_id;
END P_PARAMETER_OUT;
① SQL工作表中调用:
DECLARE
--接受存储过程返回结果
v_name user_tab.username%TYPE;
v_age user_tab.username%TYPE;
BEGIN
p_parameter_out(1001,v_name,v_age);
DBMS_OUTPUT.PUT_LINE('v_name '||v_name||',v_age '||v_age);
END;
输出:v_name chenglong,v_age 40
9.5 Java 程序调用
创建java 项目
新建lib 文件夹,并将 ojdbc.jar 文件复制到该文件夹中,ojdbc.jar 右键Add as Library ⇒ OK
创建Class
public class test {
@Test
public void test() throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("oracle.jdbc.OracleDriver");
// 2.获取连接对象
String url = "jdbc:oracle:thin:@localhost:1521:chengyu";
String user = "ADMINUSER";
String password = "Aa135007";
Connection conn = DriverManager.getConnection(url,user,password);
// 3. 获得语句对象
String sql = "Call P_PARAMETER_OUT(?,?,?)";
CallableStatement call = conn.prepareCall(sql);
// 4.设置输入参数
call.setInt(1,1001);
// 5.注册输出参数
call.registerOutParameter(2, OracleTypes.VARCHAR);
call.registerOutParameter(3, OracleTypes.NUMBER);
// 6. 执行存储过程
call.execute();
// 7.获取输出参数
String u_name = call.getString(2);
int u_age = call.getInt(3);
System.out.println("u_name="+u_name+",u_age="+u_age);
// 8.释放资源
call.close();
conn.close();
}
}
输出:u_name=chenglong,u_age=40
10. 存储函数
相对于存储过程,存储函数可以向调用者返回数据。
10.1 语法
注意:DECLARE 关键字省略
CREATE OR REPLACE FUNCTION 存储函数名
RETURN 返回类型
IS
-- 声明变量、记录类型、游标等
BEGIN
Exception
END PROCEDURE1;
10.2 无参
定义:
create or replace function hello_world
RETURN varchar2
IS
BEGIN
RETURN 'Hello World';
END;
调用:
begin
DBMS_OUTPUT.PUT_LINE(hello_world);
end;
-- select hello_world from dual
10.3 带参数
定义:
create or replace function hello_world(v_logo varchar2)
RETURN varchar2
IS
BEGIN
RETURN 'Hello:'||v_logo;
END;
调用:
begin
DBMS_OUTPUT.PUT_LINE(hello_world('chenglong'));
end;
select hello_world('chengcheng') from dual
11. 触发器
是许多关系型数据库系统都提供的一项技术,由一个事件来启动运行,不能接受参数。
11.1 触发器组成
触发事件:在什么情况下触发,如:INSERT、UPDATE、DELETE;
触发时间:触发事件发生前(before)还是发生后(after);
触发器本身:该触发器要做什么;
触发频率:动作执行的次数,语句级(STATEMENT)触发器和行级(ROW)触发器。
11.2 创建
create or replace TRIGGER update_menu_triger
after update on menu
for each row -- 每行都执行,去掉之后只执行一次
begin
DBMS_OUTPUT.PUT_LINE('============触发器执行==============');
END;
create or replace TRIGGER "ADMINUSER".trigcer_user_tab
before insert on user_tab
begin
DBMS_OUTPUT.PUT_LINE('============触发器执行==============');
if(to_char(sysdate,'DY') in ('星期六','星期日')) or
(to_char(sysdate,'HH24:MI') not between '08:00' and '18:00') THEN
raise_application_error(-20500,'只能在工作时间对该表进行操作!');
End if;
END;