PL/SQL语言(Procedural Language/SQL)是结合了结构化查询与Oracle自身过程控制为一体的强大语言,PL/SQL不但支持更多的数据类型,拥有自身的变量声明、赋值语句,而且还有条件、循环等流程控制语句。过程控制结构与SQL数据处理能力(DML) 无缝的结合形成了强大的编程语言,可以创建过程和函数以及程序包。
PL/SQL是一种块结构的语言,它将一组语句放在一个块中,一次性发送给服务器,PL/SQL引擎分析收到 PL/SQL语句块中的内容,把其中的过程控制语句由PL/SQL引擎自身去执行,把PL/SQL块中的SQL语句交给服务器的SQL语句执行器执行。如图所示:
PL/SQL块发送给服务器后,先被编译然后执行,对于有名称的PL/SQL块(如子程序—即存储函数和存储过程) 可以单独编译,永久的存储在数据库中,随时准备执行。
支持 SQL:PL/SQL支持所有的SQL数据操纵命令、游标控制命令、事务控制命令、SQL 函数、伪列。
支持面向对象编程:在PL/SQL中可以创建类型,可以对类型进行继承,可以在子程序中重载方法等
更好的性能:SQL是非过程语言,只能一条一条执行。PL/SQL把一个PL/SQL块统一进行编译后执行,同时还可以把编译好的PL/SQL块存储起来,以备重用
可移植性:使用PL/SQL编写的应用程序,可以移植到任何操作系统平台上的Oracle服务器,同时还可以编写可移植程序库,在不同环境中重用。
安全性:可以通过存储过程对客户机和服务器之间的应用程序逻辑进行分隔,这样可以限制对Oracle数据库的访问,数据库还可以授权和撤销其他用户访问的能力
PL/SQL是一种块结构的语言,一个PL/SQL程序包含了一个或者多个逻辑块(嵌套),每个块都可以划分为三个部分。
类型 | 符号 |
---|---|
赋值运算符 | := |
连接运算符 | || |
标签分隔符,只为了标识程序特殊位置。 | << >> |
范围操作符,比如 1…5 标识从 1 到 5 | .. |
求幂运算符,比如:3**2=9 | ** |
算术运算符 | +,-,*,/ |
关系运算符 | >,<,>=,<=,=,<>,!= |
逻辑运算符 | AND,OR,NOT |
比较运算符 | LIKE,BETWEEN,IN,IS NULL |
使用GOTO可以跳到标记<< outer >>的位置。标签后必须紧跟可执行语句或者 PL/SQL 块。GOTO 不能跳转到 IF 语句、CASE 语句、LOOP 语句、或者子块中。举例如下:
DECLARE
BEGIN
FOR i IN 1..100 LOOP
IF i > 10 THEN
GOTO outer;
END IF;
END LOOP;
<<outer>>
dbms_output.put_line('loop 循环了10次提前结束了!' );
END;
比较运算符可用于判断语句
//更多请阅读:https://www.yiibai.com/plsql/plsql_comparison_operators.html
DECLARE
PROCEDURE compare (value VARCHAR2, pattern VARCHAR2) is
BEGIN
IF value LIKE pattern THEN
dbms_output.put_line ('True');
ELSE
dbms_output.put_line ('False');
END IF;
END;
BEGIN
compare('Zara Ali', 'Z%A_i');
compare('Nuha Ali', 'Z%A_i');
END;
1、打开SQL PLUS并登录,输入以下命令:
set serverout on;
set serveroutput on;
DECLARE
out_text VARCHAR2(20);
BEGIN
out_text := 'hello world!';
dbms_output.put_line(out_text);
EXCEPTION
WHEN others THEN
dbms_output.put_line('有异常');
END;
2、输入“/”显示执行结果:
DECLARE
v_name VARCHAR2(20) := 'jerry';
message VARCHAR2(30) := 'What''s yiibai.com!'; --要在字符串文字中嵌入单引号,请将两个单引号放在一起
BEGIN
SELECT ename INTO v_name FROM emp WHEARE empno = '1001'; --将SQL查询结果分配给PL/SQL变量
dbms_output.put_line(v_name);
dbms_output.put_line(message); --What's yiibai.com!
EXCEPTION
WHEN others THEN
dbms_output.put_line('异常');
END;
PL/SQL是强类型的语言,使用变量时必须指定变量的数据类型。PL/SQL常见的数据类型有:
%ROWTYPE 和 %TYPE称之为属性类型。可以使用%TYPE 和 %ROWTYPE来声明变量,而不必指定特定的数据类型。
1、ROWTYPE类型使用
DECLARE
myemp emp%ROWTYPE;
BEGIN
SELECT * INTO myemp FROM emp WHERE empno='1001';
dbms_output.put_line(myemp.ename);
EXCEPTION
WHEN others THEN
dbms_output.put_line('异常');
END;
2、TYPE类型使用:
%TYPE引用某个变量或者数据库的列的类型作为某变量的数据类型。
DECLARE
v_name emp.ename%TYPE;
BEGIN
SELECT ename INTO v_name FROM emp WHERE empno='1001';
dbms_output.put_line(v_name);
EXCEPTION
WHEN others THEN
dbms_output.put_line('异常');
END;
用于存放多个数值的变量
种类:PL/SQL记录(record)、PL/SQL集合(PL/SQL表(别名索引表)、嵌套表、VARRAY)
记录是可以容纳不同种类的数据项的数据结构。记录由不同的字段组成,类似于数据库表的一行。
PL/SQL可以处理以下类型的记录:基于表的记录、基于游标的记录、用户定义的记录
1、基于表的记录
customer_rec customers%ROWTYPE; --customers是表
2、基于游标的记录
CURSOR customer_cur
IS
SELECT id, name, address
FROM customers;
customer_rec customer_cur%ROWTYPE;
3、我们使用 TYPE关键字来自定义记录类型,其语法为:
TYPE 记录类型名 IS RECORD
(
变量1 数据类型 [NOT NULL] [:= value]
变量2 数据类型 [NOT NULL] [:= value]
……
变量n 数据类型 [NOT NULL] [:= value]
);
DECLARE
TYPE emp_type IS RECORD
(
empno emp.empno%TYPE,
ename emp.ename%TYPE
);
v_emp emp_type;
BEGIN
SELECT empno,ename INTO v_emp FROM emp WHERE empno='1001'; --将查找到数据赋值给record变量
dbms_output.put_line(v_emp.empno || '-' || v_emp.ename);
EXCEPTION
WHEN others THEN
dbms_output.put_line('异常');
END;
1)一般在PL/SQL块中从表中取出一行进行处理时用PL/SQL RECORD。
2)SELECT语句查询的列值的数量与顺序,必须与创建记录类型变量中定义的字段数量和顺序相匹配。
3)一个记录类型的变量仅仅能保存从数据库中查询出的一行记录,若查询出了多行记录。就会出现错误。
访问字段
DECLARE
TYPE books IS RECORD
(
title VARCHAR(50),
author VARCHAR(50)
);
book1 books;
book2 books;
BEGIN
-- Book 1 specification
book1.title := 'C Programming';
book1.author := 'TanHao';
-- Book 2 specification
book2.title := 'Telecom Billing';
book2.author := 'LiDawei';
-- Print book 1 record
dbms_output.put_line('Book 1 title : '|| book1.title);
dbms_output.put_line('Book 1 author : '|| book1.author);
-- Print book 2 record
dbms_output.put_line('Book 2 title : '|| book2.title);
dbms_output.put_line('Book 2 author : '|| book2.author);
END;
/* 结果
Book 1 title : C Programming
Book 1 author : TanHao
Book 2 title : Telecom Billing
Book 2 author : LiDawei
*/
将记录作为子程序参数
可以像传递任何其他变量一样将记录作为子程序参数传递。还可以像访问上面的示例一样访问记录字段
DECLARE
TYPE books IS RECORD
(
title VARCHAR(50),
author VARCHAR(50)
);
book1 books;
book2 books;
-- 在DECLARE中声明临时子程序
PROCEDURE printbook (book books) IS
BEGIN
dbms_output.put_line ('Book title : ' || book.title);
dbms_output.put_line('Book author : ' || book.author);
END;
BEGIN
-- Book 1 specification
book1.title := 'C Programming';
book1.author := 'Haoqiang Tang';
-- Book 2 specification
book2.title := 'Telecom Billing';
book2.author := 'Maxsu';
-- Use procedure to print book info
printbook(book1);
printbook(book2);
END;
/* 结果
Book 1 title : C Programming
Book 1 author : TanHao
Book 2 title : Telecom Billing
Book 2 author : LiDawei
*/
类型 | 可存储于数据库 | 元素个数 | 是否需初始化 | 初始下标值 |
---|---|---|---|---|
PL/SQL表 | 否 | 无限 | 不需 | 无 |
嵌套表 | 可 | 无限 | 需 | 1 |
可变数组 | 可 | 有限(自定义) | 需 | 1 |
PL/SQL表(索引表)、嵌套表、varry 三个都可以作为集合类型来存放数据,只不过它们本身又具有独特的一些特性。
个人认为,如果仅仅是在存储过程中当做集合变量使用,PL/SQL表(索引表)是最好的选择。
另外Oracle专门为集合类型(PL/SQL表(别名索引表)、嵌套表、VARRAY)提供了集合变量的方法及嵌套调用等一系列特征,将在本博客单独一遍讲述。
PL/SQL索引表
不能存储于数据库中,元素个数没有限制,下标可以为负值。
--element_type:指定索引表**元素的数据类型**
--NOT NULL表示不允许引用NULL元素
--key_type指定索引表元素**下标的数据类型**(binary_integer、pls_integer、varchar2(9i后允许))。
TYPE type_name is table of element_type [NOT NULL] INDEX BY key_type;
1)使用binary_integer或pls_integer定义PL/SQL索引表下标。
--示例说明:emp.ename%type表示存放元素的数据类型,binary_integer表示元素下标的数据类型
declare
type v_table is table of emp.ename%type index by binary_integer;
st v_table;
begin
select ename into st(-1) from emp where empno=28; --元素值为字段ename的值,下标为-1
end;
declare
type emp_table_type is table of emp%rowtype index by binary_integer;
et emp_table_type;
begin
select * into et(1) from emp where empno=&no;
dbms_output.put_line('姓名:'||et(1).ename);
dbms_output.put_line('工资:'||et(1).sal);
dbms_output.put_line('岗位:'||et(1).job);
dbms_output.put_line('时间:'||et(1).hiredate);
end;
2)使用varchar2定义PL/SQL索引表的下标
declare
type areaTableType is table of number index by varchar(10);
atab areaTableType;
begin
atab('japan'):=1;
atab('China'):=2;
atab('American'):=3;
atab('England'):=4;
atab('Portugal'):=5;
dbms_output.put_line('第一个元素'||atab.first);
dbms_output.put_line('最后一个元素'||atab.last);
end;
嵌套表
定义:Nested Table,PL/SQL索引表没有index by子句就是嵌套表。嵌套表简单来说就是把一个表中的字段定义为一个表,这个字段表的数据存储在外部的一个表中。
说明:数组类型,元素个数无限制,下标从1开始,元素存储可以是稀疏的,使用前必须使用其构造方法进行初始化。(嵌套表可以作为列的数据类型,但PL/SQL索引表不行)
语法:type type_name is table of element_type; --element_type指定元素的数据类型。
例一:可以单独的操作嵌套表
declare
type ename_table_type is table of emp.ename%type; --定义嵌套表类型
et ename_table_type; --声明
begin
et:=ename_table_type('MARY','CLARK','DOG'); --初始化3个元素(对嵌套表进行初始化可以使用它的构造函数)
select ename into et(2) from emp where empno=&no;
dbms_output.put_line('雇员名:'||et(2)); --输出是CLARK
end;
例二:对包含嵌套表的表的操作
create or replace type phone_type is table of varchar2(20); --定义嵌套表类型
create table person
(
id number(4),
name varchar2(10),
sal number(6,2),
phone phone_type
)nested table phone store as phone_table; --声明嵌套表类型,可以理解为表person中phone列保存着一个一维数组。
--inset
begin
insert into person values(1,'scott',800,phone_type('0471-3456788','13804171235'));
end;
--select
declare
pt phone_type;
begin
select phone into pt from person where name = 'scott';
for i in 1..pt.count loop
dbms_output.put_line('第'||i||'个号码:'||pt(1));
end;
--update
declare
pt phone_type := phone_type('025-58843287','15805175201','0714-6575787');
begin
update person set phone = pt where id = 1;
end;
例三:嵌套表作为列的数据类型例子,建立对象类型emptype,嵌套表类型emparray,表类型department
create or replace type emptype as object --创建对象类型
(
name varchar2(10),
salary number(6,2),
hire datedate
);
create or replace type emparray is table of emptype; --根据对象类型创建嵌套表
create table department --创建表类型department,嵌套表作为表列
(
deptno number(2),
dname varchar2(10),
employee emparray
) nested table employee store as employee_table; --可以理解表department中employee列保存着一个二维数组。
--insert
insert into temp_department
values (1,'scott',temp_emparray(temp_emptype('0471',2, sysdate),temp_emptype('0473', 3, sysdate)));
varray
说明:变长数组,可以作为列和对象类型属性的数据类型,元素个数有限制,下标从1开始,使用前需要使用其构造方法初始化。
语法:TYPE type_name is varray(size_limit) of element_type [not null];
--size_limit指定元素个数,element_type指定元素的数据类型。
例一:在PL/SQL块中使用varray
declare
type job_array_type is varray(20) of emp.job%type;
jarr job_arry_type:=job_array_type('HRQ','CLERK'); --初始化2个元素
begin
select job into jarr(1) from emp where lower(ename)=lower('&name'); --覆盖了初始化的数值
dbms_output.put_line('岗位:'||jarr(1));
end;
例二:phone_array作为worker表中某一列的数据类型,varchar(20)为phone_array数组的元素类型
create type phone_array is varray(20) of varchar(20);
create table worker(id number(4), name varchar2(10), sal number(6,2), phonephone_arra);
例三:article_array作为author表中某一列的数据类型,article_type为article_array数组的元素类型
create type article_type as object(title varchar2(30), pubdate date);
create type article_array is varray(20) of article_type;
create table author(id number(6), name varchar2(10), article article_array);
PL/SQL 程序可通过顺序、条件或循环结构来控制命令执行的流程。
1、 IF-THEN-END IF
DECLARE
newsal emp.sal%TYPE;
BEGIN
SELECT sal INTO newsal FROM emp WHERE ename = 'James';
IF newsal >= 1500 THEN
UPDATE emp SET comm = 1000 WHERE ename = 'James';
END IF;
COMMIT;
END;
2、IF-THEN-ELSE-END IF
DECLARE
newsal emp.sal%TYPE;
BEGIN
SELECT sal INTO newsal FROM emp WHERE ename = 'James';
IF newsal >= 1500 THEN
UPDATE emp SET comm = 1000 WHERE ename = 'James';
ELSE
UPDATE emp SET comm = 100 WHERE ename = 'James';
END IF;
COMMIT;
END;
3、IF-THEN-ELSIF-THEN-ELSE-END IF
DECLARE
newsal emp.sal% TYPE;
BEGIN
SELECT sal INTO newsal FROM emp WHERE ename='James';
IF newsa l>= 1500 THEN
UPDATE emp SET comm = 1000 WHERE ename='James';
ELSE newsal >= 1000 THEN
UPDATE emp SET comm = 800 WHERE ename='James';
ELSIF
UPDATE emp SET comm = 400 WHERE ename='James';
END IF;
COMMIT;
END;
4、CASE-WHEN-THEN-ELSE-END CASEZ(有 简单CASE语句 和 可搜索CASE语句 之分)
简单CASE语句
--CASE 中存在 selector,不返回值
DECLARE
v_grade VARCHAR2(10) := upper('&p_grade');
BEGIN
CASE v_grade
WHEN 'A' THEN
dbms_output.put_line('A');
WHEN 'B' THEN
dbms_output.put_line('B');
WHNE 'C' THEN
dbms_output.put_line('C');
ELSE
dbms_output.put_line('not found char!');
END CASE;
END;
--CASE 中存在 selector,作为表达式使用
DECLARE
v_grade CHAR(1):=UPPER('&grade');
p_grade VARCHAR(20) ;
BEGIN
p_grade :=
CASE v_grade
WHEN 'A' THEN 'Excellent'
WHEN 'B' THEN 'Very Good'
WHEN 'C' THEN 'Good'
ELSE 'No such grade' END;
dbms_output.put_line('Grade:' ||v_grade||',the result is '||p_grade);
可搜索CASE语句
DECLARE
grade CHAR(1) := 'B';
BEGIN
CASE
WHEN grade = 'A' THEN dbms_output.put_line('Excellent');
WHEN grade = 'B' THEN dbms_output.put_line('Very good');
WHEN grade = 'C' THEN dbms_output.put_line('Well done');
WHEN grade = 'D' THEN dbms_output.put_line('You passed');
WHEN grade = 'F' THEN dbms_output.put_line('Better try again');
WHEN dbms_output.put_line('No such grade');
END CASE;
END;
1、LOOP-END LOOP(死循环,配合EXIT或者EXIT WHEN使用)
配合EXIT
DECLARE
v_sum NUMBER(10) := 0;
i NUMBER(10) := 1;
BEGIN
LOOP
v_sum := v_sum + i;
i := i+ 1;
IF i > 100 THEN
EXIT;
END IF;
END LOOP;
dbms_output.put_line(v_sum);
END;
配合EXIT WHEN
DECLARE
cou NUMBER ;
BEGIN
cou := 1 ;
LOOP
dbms_output.put_line('cou = '||cou) ;
EXIT WHEN cou > 10 ;
cou := cou + 1 ;
END LOOP ;
END ;
2、WHILE-LOOP-END LOOP
DECLARE
v_sum NUMBER(10) := 0;
i NUMBER (10) := 1;
BEGIN
WHILE i <= 100 LOOP
v_sum := v_sum + i ;
i := i + 1;
END LOOP;
dbms_output.put_line(v_sum);
END;
3、FOR-IN-LOOP-END LOOP(正向FOR LOOP语句 和 反转FOR LOOP语句)
正向FOR LOOP语句
DECLARE
v_sum NUMBER (10) := 0;
i NUMBER (10) := 1;
BEGIN
FOR i IN 1..100 LOOP
v_sum : = v_sum + i;
END LOOP;
dbms_output.put_line(v_sum);
END;
反转FOR LOOP语句
DECLARE
a number(2) ;
BEGIN
FOR a IN REVERSE 10 .. 20 LOOP
dbms_output.put_line('value of a: ' || a);
END LOOP;
END;
在 PL/SQL 程序开发中,可以使用 DML 语句和事务控制语句,但是还有很多语句(比如 DDL 语句)不能直接在 PL/SQL 中执行,这些语句可以使用**动态 SQL **来实现。
PL/SQL 块先编译然后执行,动态 SQL 语句在编译时不能确定,只有在程序执行时把 SQL 语句作为字符串的形式由动态 SQL 命令来执行。
在编译阶段,动态SQL 语句作为字符串存在,程序不会对字符串中的内容进行编译,在运行阶段再对字符串中的 SQL 语句进行编译和执行。
动态 SQL语法格式:
EXECUTE IMMEDIATE 动态语句字符串 --动态SQL中的参数格式是:[:参数名],[:参数名]可以是[:数字]也可以是[:字符串]
[INTO 变量列表] --如果动态语句是SELECT语句,可以把查询的结果保存到INTO后面的变量中。
[USING 参数列表] --如果动态语句中存在参数,USING为语句中的参数传值。
DECLARE
sql_stmt VARCHAR2(200); --动态SQL语句
emp_id NUMBER(4) := 7566;
salary NUMBER(7,2);
dept_id NUMBER(2) := 90;
dept_name VARCHAR2(14) := 'PERSONNEL';
location VARCHAR2(13) := 'DALLAS';
emp_rec emp%ROWTYPE;
BEGIN
--无子句的execute immediate
EXECUTE IMMEDIATE 'CREATE TABLE bonus1 (id NUMBER, amt NUMBER)';
----using子句的execute immediate
sql_stmt := 'INSERT INTO dept VALUES (:1, :2, :3)';
EXECUTE IMMEDIATE sql_stmt USING dept_id, dept_name, location;
----into子句的execute immediate
sql_stmt := 'SELECT * FROM emp WHERE empno = :id';
EXECUTE IMMEDIATE sql_stmt INTO emp_rec USING emp_id;
----returning into子句的execute immediate
sql_stmt := 'UPDATE emp SET sal = 2000 WHERE empno = :1 RETURNING sal INTO :2';
EXECUTE IMMEDIATE sql_stmt USING emp_id RETURNING INTO salary;
EXECUTE IMMEDIATE 'DELETE FROM dept WHERE deptno = :num' USING dept_id;
END;
1)在 Oracle 的 insert,update,delete 语句都可以使用 RETURNING 子句把操作影响的行中的数据返回。使用该字句的效果,与进行insert、update之后执行select into,以及在delete之前进行select into的效果相同。
2)对 SQL 语句中存在 RETURNING 子句时,在动态执行时可以使用RETURNING INTO 来接收。
RETURNING子句详细见:https://blog.csdn.net/helloboat/article/details/43483071