1. PLSQL概述
PLSQL是Oracle内部的一种编程语言。
PLSQL是一门语言。叫做过程化SQL语言(Procedural Language SQL)
PLSQL是一种过程化语言,属于第三代语言,它与C、C++、Java等语言一样关注于处理细节,可以用来实现比较复杂的业务逻辑。
PL/SQL是对结构化查询语言(SQL)的过程语言扩展。
PL/SQL的基本单位叫做一个区段,由三个部分组成:一个声明部分,一个可运行部分,和排除-构建部分。
PL/SQL区段只被编译一次并且以可运行的形式储存,以降低响应时间。
实际工作中,PLSQL多用于写触发器、存储过程、函数等。
2. PLSQL基本语法
PL/SQL的基本单位叫做一个区段,由三个部分组成:一个声明部分,一个可运行部分,和排除-构建部分。
这里声明只有运行部分是必须的,其余均是可选,如下:
declare --(可选,声明变量用)
begin --(must)
null;
exception --(可选)
end; --(must)
2.1 简单的PSQL语句块
2.1.1 null语句块
如下我们创建一个最简单的PLSQL语句块
begin
null;
end;
/
在命令行执行,结果如下:
需要指出的是,这里的 null 不可以省略,PLSQL语句块中必须包含一条语句。
2.1.2 Hello World语句
下面我们写一个最简单并且打印Hello World的PLSQL。
set serveroutput on; --用于打开控制台输出服务,默认是off,则不打印
begin
dbms_output.put_line('HelloWorld'); --类似于java的System.out.print
end;
/
在命令行执行,结果:
2.2 含有声明(declare)语句的PLSQL
declare用于声明变量。
变量名称与变量类型不可省略,默认值通过 :=
赋值,默认值可以省略。
格式如下:
declare
v_variable1 variable_type [ := default_value];
v_variable2 variable_type [ := default_value];
2.2.1 PLSQL变量类型
2.2.1.1 变量命名
变量声明
规则:
- 变量不能使用保留字。如from select;
- 第一个字符必须是字母,一般以
v_
开头; - 变量名最多包含30个字符;
- 不要与数据库的表或者列同名;
- 每一行只能声明一个变量。
2.2.1.2 基本变量类型7个
PLSQL的基本变量类型有7个。如下:
- binary_integer:整数,主要用来计数而不是用来表示字段类型。
- number:数字类型
- char:定长字符串
- varchar2:变长字符串
- date:日期
- long:长字符串,最长2GB
- boolean:布尔类型,可以取值true、false和null
根据以上类型,我们写一个含有变量声明的PLSQL。
declare
v_temp number(1);
v_count binary_integer := 0;
v_sal number(7,2) :=4000.00;
v_date date := sysdate;
v_pi constant number(3,2) := 3.14; --类似于Java的final关键字,表示常量
v_valid boolean := false;
v_name varchar2(20) not null := 'MyName';
begin
dbms_output.put_line('v_temp value: '||v_temp);
dbms_output.put_line('v_count value: '||v_count);
dbms_output.put_line('v_sal value: '||v_sal);
dbms_output.put_line('v_date value: '||v_date);
dbms_output.put_line('v_pi value: '||v_pi);
-- dbms_output.put_line('v_valid value: '||v_valid); --不能打印boolean值
dbms_output.put_line('v_name value: '||v_name);
end;
命令行结果如下:
这里需要指出的是,PLSQL的dbms_output.put_line()
函数不能直接打印boolean类型。
2.2.1.3 关联变量类型(关联别的字段、变量的变量类型)
在工作中,变量的作用更多的是存储某个字段中的值,因而保证变量数据类型与字段数据类型一致是很有必要的。
PLSQL中,在变量声明时,可以通过%type
获得指定字段(变量)的数据类型。这种方式可以确保当源表的字段类型改变时,变量的类型自动改变。
如下:
declare
v_empno number(4);
v_empno2 emp.empno%type; --v_empno2的类型是表emp的empno字段的类型
v_empno3 v_empno2%type; --v_empno3的类型是变量v_empno2的类型
2.2.1.4 复合变量
PLSQL提供了两种复合变量。
- Table 类似于Java的数组Array
- record 类似于Java的类Class
如下声明一个Table变量
declare
type type_table_emp_empno is table of emp.empno%type index by binary_integer; --声明一个数组类型
v_empnos type_table_emp_empno; --利用类型声明变量
begin
v_empno(0) := 7369;
v_empno(2) := 7839;
v_empno(-1) := 9999; --Table类型的下标可以是负数
dbms_output.put_line(v_empno(-1));
end;
声明一个Record类型
--RECORD类型
declare
type type_record_dept is record
(
deptno dept.deptno%type,
dname dept.dname%type,
loc dept.loc%type
);
v_temp type_record_dept;
begin
v_temp.deptno := 50;
v_temp.dname := 'aaa';
v_temp.loc := 'bj';
dbms_output.put_line(v_temp.deptno||v_temp.dname||v_temp.loc);
end;
这里如果表的结构发生了变化,我们如何保证声明的复合变量和表中的变量相同呢,可以通过rowtype实现。
%rowtype:定义一个表示表中一行记录的变量
如下:
declare
v_temp dept%rowtype;
begin
v_temp.deptno := 50;
v_temp.dname := 'aaa';
v_temp.loc := 'bj';
dbms_output.put_line(v_temp.deptno||v_temp.dname||v_temp.loc);
end;
这里关于 %type
和 %rowtype
做一个简单的对比说明:
如下:
%TYPE
为了使一个变量的数据类型与另一个已经定义了的变量(尤其是表的某一列)的数据类型相一致,当被参照的那个变量的数据类型改变了之后,这个新定义的变量的数据类型会自动跟随其改变,无需修改PL/SQL程序。当不能确切地知道被参照的那个变量的数据类型时,就只能采用这种方法定义变量的数据类型。
%ROWTYPE
如果一个表有较多的列,使用%ROWTYPE来定义一个表示表中一行记录的变量,比分别使用%TYPE来定义表示表中各个列的变量要简洁得多,并且不容易遗漏、出错。这样会增加程序的可维护性。当表的某些列的数据类型改变了之后,这个新定义的变量的数据类型会自动跟随其改变,无需修改PL/SQL程序。当不能确切地知道被参照的那个表的结构及其数据类型时,就只能采用这种方法定义变量的数据类型。
2.3 含有异常处理的PLSQL
异常块通过exception关键字声明,如下:
declare
v_num number := 0;
begin
v_num := 2/v_num;
dbms_output.put_line(v_num);
exception
when others then
dbms_output.put_line('error');
end;
/
3.PLSQL中的SQL语法
3.1 DML语句(数据操作语言,select,update,insert,delete from)
PLSQL中可以写SQL语句,和平时的SQL语法基本一致,需要注意的是以下几点:
- select语句必须返回一条记录,并且只能返回一条记录;
- select必须和into一块用(除非使用游标);
- 操作的数据可以是变量。
- insert、update、delete等语句与普通SQL完成一致,只是可以传入参数而已。
这里需要解释的是,select语句不返回值则无意义,返回许多则变量装不了。
下面我们有一张测试表,并且根据这张表,实验理解PLSQL的SQL语句。
set serveroutput on; --该服务开启一次即可,如果之前开启过无需再次开启。
declare
v_dep ljb_test.dep%type; --type
v_name ljb_test.name%type; --type
v_test ljb_test%rowtype; --rowtype
--测试insert&update的数据
v_new_dep ljb_test.dep%type := 10;
v_new_name ljb_test.name%type := '小明';
v_new_salary ljb_test.salary%type := 5000;
v_anoter_name ljb_test.name%type := '大明';
begin
--select语句必须返回一条记录,并且只能返回一条记录
select dep, name into v_dep, v_name from ljb_test where salary = 6000; --限定条件一定要保证返回一条记录
dbms_output.put_line(v_dep||v_name);
select * into v_test from ljb_test where salary = 6000; --限定条件一定要保证返回一条记录
dbms_output.put_line(v_test.dep||v_test.name||v_test.salary);
--insert,与普通SQL语句一致,只是可以插入变量
insert into ljb_test values(v_new_dep, v_new_name, v_new_salary);
commit; --执行完插入语句记得提交事务
--update,与普通SQL语句一致,只是可以插入变量
update ljb_test t set name = v_anoter_name where t.name = v_new_name;
--delete,与普通SQL语句一致,只是可以插入变量
delete from ljb_test t where t.dep = '1'; --删除部门1的记录
end;
/
输出结果:
同时我们看下更新后的测试表:
可以看到数据已经更新。
3.2 DDL(数据定义语言)
DDL语句不能直接执行,必须使用 execute immediate ''
进行包裹;
如下,我们通过PLSQL创建一张表:
begin
execute immediate 'create table tb(aaa varchar2(255) default ''asd'')'; --两个单引号代表一个单引号
end;
/
需要指出的两点:
- DDL必须被
execute immediate ''
包裹,否则报错ORA-06550; - 平时的语句中的单引号必须通过两个单引号进行表示,如以上脚本的默认值。
- delete语句可以不使用execute immediate包裹。
我们在命令行执行,并查看该表
同样的,truncate和drop写法如下:
begin
execute immediate 'truncate table tb'; --删除记录,释放表空间
execute immediate 'drop table tb'; --删除记录及定义,释放表空间
end;
/
但是有个例外:就是delete。
delete既可以使用execute也可以不使用,如下两种写法均是正确的。
begin
execute immediate'delete tb'; --delete删除记录,但不释放表空间
commit;
delete tb;
commit;
end;
/
这里可以这样理解,实际上delete table_name
等价于 delete from table_name
,是一种DML语言,因而可以直接执行。
这里针对drop、truncate、delete在复习下三者的区别
- TRUNCATE TABLE:删除内容、释放空间但不删除定义。
- DELETE TABLE:删除内容不删除定义,不释放空间。
- DROP TABLE:删除内容和定义,释放空间。
4.PLSQL的判断循环语句
判断循环语句是PLSQL的重要语句。
4.1 判断(分支)语句
判断语句也叫做分支语句,通过if实现。如下通过一个简单实例进行理解:
对于如下表:
我们写一个薪水等级判断语句:
<3000 low
3000~5000 middle
其余 high
--if语句
declare
v_sal ljb_test.salary%type;
begin
select salary into v_sal from ljb_test where name = 'ri';
if(v_sal < 3000) then
dbms_output.put_line('low');
elsif(v_sal < 5000) then --elsif
dbms_output.put_line('middle');
else --else后面没有then
dbms_output.put_line('high');
end if;
end;
/
set serveroutput on; --也可以放在下面,但是需要在之前增加一个 /
/
和Java等语句的if语句极其相似,不同的是具体的语法,需要注意其特点。
输出结果如下:
4.2 循环语句
与Java中的循环语句类似,PLSQL也要拥有类似的三种循环语句。为了方便理解,我使用Java循环的区分方式来区分PLSQL的三种循环。
即:
do while循环
while循环
for循环
对于三种循环,均具有以下特点:
PLSQL的循环一定是以loop开头,以end loop结束。
4.2.1 do while循环
先打印,然后在判断条件,如下:
--类似于do while 循环
set serveroutput on;
declare
i binary_integer := 1; --声明一个计数变量
begin
loop --循环总是loop开头
dbms_output.put_line(i);
i := i + 1;
exit when(i>=11); --循环退出语句
end loop; --循环总是end loop结束
end;
/
命令行打印结果如下:
4.2.2 while循环
先判断,在循环打印
--while循环
declare
j binary_integer := 1; --声明一个计数变量
begin
while j<11 loop --循环退出语句,循环总是loop开头
dbms_output.put_line(j);
j := j+1;
end loop; --循环总是end loop结束
end;
/
结果如下:
4.2.3 for循环
for循环无需declare块声明变量。
1..10表示1-10,注意是两个点。
--for循环
begin --for循环无需declare声明变量
for k in 1..10 loop --1..10表示1-10,注意是两个点
dbms_output.put_line(k);
end loop;
for k in reverse 1..10 loop --reverse表示逆序
dbms_output.put_line(k);
end loop;
end;
/
结果如下:
5. PLSQL异常处理
前面说过,PLSQL是一门过程语言,所以也支持类似于Java的异常处理机制。
需要指出的是,PLSQL的异常处理实际工作中并不常用,原因很简单,为了提高程序的可移植性(用于多个数据库),我们通常把异常处理放在Java等语言中处理。
异常处理的核心应用一般是日志表。
所以,下文我们仅作简单的介绍。
5.1异常处理的是运行时异常,而非编译时异常
首先,明确一点,异常处理只能捕获运行时的异常,编译时的异常无法捕获,因而如果存在编译异常,则程序无法运行,更无法调用异常处理机制。
begin
insert into dual values('',''); --插入字段数明显不符合
exception
when others then
dbms_output.put_line('ERROR!');
end;
/
这时,程序无法编译通过,会直接报错:
5.2 常见的异常类型及处理
下面,我们就之前PLSQL中select语句返回一条记录的要求,来写几个简单的异常捕获程序:
5.2.1 too_many_rows
set serveroutput on;
declare
v_name ljb_test.name%type;
begin
select name into v_name from ljb_test;
exception
when too_many_rows then --too_many_rows,返回值超过一条
dbms_output.put_line('返回值太多');
when others then
dbms_output.put_line('error');
end;
/
运行程序,结果如下:
5.2.2 no_data_found
declare
v_name ljb_test.name%type;
begin
select name into v_name from ljb_test where 1 = 0;
exception
when no_data_found then --no_data_found,无数据异常
dbms_output.put_line('无数据');
when others then
dbms_output.put_line('error');
end;
/
结果如下:
5.3 日志表创建
异常处理最核心的应用应该就是日志表,而日志表多用于存储过程等。
- 首先创建一个日志表:
create table err_log(
err_id number primary key,
err_code number,
err_msg varchar2(1024),
err_date date
);
- 创建sequence
create sequence seq_err_log start with 1 increment by 1;
- 添加异常块
declare
v_errcode number;
v_errmsg varchar2(1024);
v_name ljb_test.name%type;
begin
--insert into dual values('0','2'); --编译时错误是无法通过异常捕获处理的
select name into v_name from ljb_test;
--commit;
exception
when others then
rollback; --回滚,取消错误操作的影响
v_errcode := SQLCODE; --SQLCODE,关键字,代表出错代码,Oracle的错误代码全部是负数
v_errmsg := SQLERRM; --SQLERRM,关键字,代表出错信息
insert into err_log values(seq_err_log.nextval,v_errcode,v_errmsg,sysdate);
commit; --不要忘记commit
end;
/
我们查询下对应的日志表内容:
如果是存储过程中的日志表,可以添加一个pro_name字段。
插入的时候,直接插入该存储过程的名字即可。
6.写在最后
至此,PLSQL的基本语法已经学习完成,有关存储过程、游标等的介绍,将会另起一文,本文仅对基本语法等做简单学习总结。