您的位置:首页 > 数据库 > Oracle

Oracle PL/SQL

2017-09-22 00:00 274 查看

一、.基本语法

PL/SQL的结构块如下:
declare
--声明变量、类型、游标
begin
--程序执行的部分,类似main方法
exception
--针对异常,提供如何处理的机制
--when ....then....
end;
注意,没有用的部分,就 不需要写,比如程序的确是没有异常要处理,那么exception就不写
建议的命名方法:
标识符 命名规则 例子
程序变量 V_name V_name
程序常量 C_Name C_company_name
游标变量 Name_cursor Emp_cursor
异常标识 E_name E_too_many
表类型 Name_table_typeEmp_record_type
表 Name_table Emp
记录类型 Name_recordEmp_record
SQL*Plus替代变量 P_name P_sal
绑定变量 G_name G_year_sal
HelloWorld演示:

begin
dbms_output.put_line('HelloWorld');
end;

输出100员工的工资:

declare
v_sal number(20) :=0;    --声明变量,要注意的是,数据类型和要查询的表中的数据类型是对应得
--附一个默认值,:=
v_email varchar2(20);--另一种写法,v_email employees.email % type
v_hire_date date;    --v_hire_date employees.hire_date % type   //v_hire_date与employees表中的hire_date类型一样
--%type ,如果自定义类型的精度不够,那么%type就是动态的获取表中的类型及精度
begin
select salary,email , hire_date into v_sal,v_email,v_hire_date
from employees where employee_id = 100;
--实现select操作,注意是select into
dbms_output.put_line(v_sal||','||v_email||','||v_hire_date);--打印
end;

修改数据

declare
v_emp_id employees.employee_id%type;
begin
v_emp_id :=100;
update employees
set salary = salary + 100
where employee_id = v_emp_id;
dbms_output.put_line('OK');
end;

使用%TYPE
定义一个变量,其数据类型与已经定义的某个 数据变量的类型相同,或者与数据库表的某个列的数据类型
相同,这时可以使用%TYPE。
使用%TYPE 特性的优点在于:
所引用的数据库列的数据类型可以不必知道;
所引用的数据库列的数据类型可以实时改变。
使用%ROWTYPE
PL/SQL 提供%ROWTYPE 操作符, 返回一个记录类型, 其数据类型和数据库表的数据结构相一致。
使用%ROWTYPE 特性的优点在于:
所引用的数据库中列的个数和数据类型可以不必知道;
所引用的数据库中列的个数和数据类型可以实时改变。


二、记录类型

type ... is record(,,);

DECLARE
--声明一个记录类型,类似于java中的类
TYPE emp_record IS RECORD(
v_sal employees.salary % type,
v_email employees.email % type,--以逗号链接
v_hire_date employees.hire_date % type);--分号
--定义一个记录变量
v_emp_record emp_record;
BEGIN
SELECT salary , email, hire_date INTO v_emp_record
FROM employees WHERE employee_id = 101;
dbms_output.put_line(v_emp_record.v_sal||','||v_emp_record.v_email||','||v_emp_record.v_hire_date);
END;

获取一个表中所有列的记录类型

declare
v_emp_record employees%rowtype;
v_emp_id number(10);
begin
v_emp_id  := 100;
select * into v_emp_record
from employees
where employee_id = v_emp_id;
dbms_output.put_line(v_emp_record.salary||' , '||v_emp_record.last_name);--用表中的列名字即可
end;


三、流程控制:

条件判断:(两种)
方式一:if ..then elseif then...else...end if;
方式二:case...when...then ..end;
循环结构:(三种)
方式一:loop...exit when ...end loop;
方式二:while..loop...end;
方式三:for i in ..loop ..end loop;
关键字:goto (类似java中的break后接一个标签,跳出那个循环)、 exit
条件判断:
--查询出 150号 员工的工资, 若其工资大于或等于 10000 则打印 'salary >= 10000';
--若在 5000 到 10000 之间, 则打印 '5000<= salary < 10000'; 否则打印 'salary < 5000'
方式一

declare
v_sal employees.salary%type;
v_result varchar2(20);
begin
select salary into  v_sal from employees where employee_id = 150;
if v_sal >= 10000 then v_result := 'salary >= 10000';
elsif v_sal >=5000 then v_result := 'salary >= 5000';
else v_result := 'salary < 5000';
end if;
dbms_output.put_line(v_sal||' , '||v_result);
end;

方式二

declare
v_sal employees.salary%type;
v_result varchar2(20);
begin
select salary into  v_sal from employees where employee_id = 150;
v_result :=
case trunc(v_sal/5000) when 2  then 'salary >= 10000'
when 1  then '5000 <= salary < 10000'
when 0  then 'salary < 5000'
end;
dbms_output.put_line(v_sal||' , '||v_result);
end;

case...when..then..相当于java的switch,使用起来有局限性,还是if..then..elsif...then比较好
注意,if..then每一次后面都要加“分号”,而case..when..then不能加,分号的问题在PL/DQL中比较坑
循环结构:
--使用循环语句打印 1 - 100.(三种方式)
--(1)初始化 (2)循环体 (3)循环条件 (4)迭代条件

declare
--(1)
v_i number(3) := 1;
/*方式一
begin
loop
--(2)
dbms_output.put_line(v_i);
--(4)
v_i := v_i + 1;
--(3)
exit when v_i >100;
end  loop;--注意每一行,都要有“分号”
end;*/
方式二:(推荐使用 )
begin
while v_i <= 100 loop
dbms_output.put_line(v_i);
v_i := v_i + 1;
end loop;
end;

/*方式三
begin
for i in 1..100 loop --for i in reverse 1..100 loop,这样就是反着,从100-1,还要注意in后面是两个“.”
dbms_output.put_line(i);
end loop;
end;
*/

输出1-100的所有质数

declare
v_i number(3) := 2;
v_j number(3) := 2;
v_flag number(3) := 1;
begin
/*while v_i<=100 loop

while v_j <= sqrt(v_i) loop
if mod(v_i,v_j)=0 then v_flag := 0;
goto OK;
end if;
v_j := v_j + 1;

end loop;
<<OK>>
if v_flag = 1 then dbms_output.put_line(v_i);
end if;
v_j := 2;
v_i := v_i + 1;
v_flag := 1;
end loop;
*/
for i in 2..100 loop
for j in 2..sqrt(i) loop
if mod(i,j)=0 then v_flag := 0;
goto OK;
end if;
end loop;
<<OK>>

if v_flag = 1 then dbms_output.put_line(i);
end if;
v_flag := 1;
end loop;
end;

对于while 循环的嵌套。特别要注意初始化条件



四、游标的使用(类似于java的iterator)

主要方便用于处理多行数据。

筛选出employees表中department_id为80的所有员工的salary,last_name.

declare
--定义一个记录类型
type v_record is record(
v_sal employees.salary%type,
v_last_name employees.last_name%type
);
--声明一个记录类型
v_emp_record v_record;
--定义一个游标
cursor emp_sal_cursor is select salary,last_name from employees where department_id = 80;
begin
--打开游标
open emp_sal_cursor;
--提取游标
fetch emp_sal_cursor into v_emp_record;
while emp_sal_cursor%found loop
dbms_output.put_line(v_emp_record.v_last_name||' : '||v_emp_record.v_sal);
fetch emp_sal_cursor into v_emp_record;
end loop;
close emp_sal_cursor;
end;

游标的使用,用for比较简单

declare
cursor emp_sal_cursor is select salary,last_name from employees where department_id = 80;
begin

for c in emp_sal_cursor loop
dbms_output.put_line(c.last_name||c.salary);
end loop;
end;

带参数的游标

declare
--定义游标
cursor emp_sal_cursor(dept_id number, sal number) is
select salary + 1000 sal, employee_id id
from employees
where department_id = dept_id and salary > sal;

--定义基数变量
temp number(4, 2);
begin
--处理游标的循环操作
for c in emp_sal_cursor(sal => 4000, dept_id => 80) loop
--判断员工的工资, 执行 update 操作
--dbms_output.put_line(c.id || ': ' || c.sal);

if c.sal <= 5000 then
temp := 0.05;
elsif c.sal <= 10000 then
temp := 0.03;
elsif c.sal <= 15000 then
temp := 0.02;
else
temp := 0.01;
end if;

dbms_output.put_line(c.sal || ': ' || c.id || ', ' || temp);
--update employees set salary = salary * (1 + temp) where employee_id = c.id;
end loop;
end;

隐式游标
隐式游标属性
SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;
SQL%NOTFOUND 布尔型属性,与%FOUND 相反;
SQL %ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;
SQL %ISOPEN 布尔型属性, 取值总是 FALSE。SQL 命令执行完毕立即关闭隐式游标。
--更新指定员工信息,如果该员工没有找到,则打印”查无此人”信息。

declare
v_sal employees.salary%type;
v_empid employees.employee_id%type := 101;
begin
update employees
set last_name = 'ABC'
where employee_id = v_empid;
if sql%notfound then --
dbms_output.put_line('查无此人');
end if;
end;


五、异常处理(三种类型)

异常处理概念
异常情况处理(EXCEPTION) 是用来处理正常执行过程中未预料的事件, 程序块的异常处理预定义的错误
和 自定义错误, 由于 PL/SQL 程序块一旦产 生异常而没有指出如何处理时,程序就会自动终止整个程序运行
共有三种类型:
1.预定义 ( Predefined ) 错误
ORACLE 预定义的异常情况大约有 24 个。对这种异常情况的处理,无需在程序中定义,由 由 ORACLE 自动
将其引发。
2.非预定义 ( Predefined )错误
即其他标准的 ORACLE 错误。对这种异常情况的处理,需要用户在程序中定义,然后由 ORACLE 自动将
其引发。
3.用户定义(User_define) 错误
程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理, 需要 用户在程序中定义,
然后显式地在程序中将其引发。
异常处理的基本结构:

EXCEPTION
WHEN first_exception THEN <code to handle first exception >
WHEN second_exception THEN <code to handle second exception >
WHEN OTHERS THEN <code to handle others exception >
END;

异常处理可以按任意次序排列,但 OTHERS 必须放在最后.
预定义异常

declare
v_sal employees.salary%type;
begin
select salary into v_sal from employees
where employee_id > 100;
dbms_output.put_line(v_sal);
exception
when too_many_rows then dbms_output.put_line('要输出的行数太多了!');
when others then dbms_output.put_line('其他错误!');
end;

非预定义异常
步骤如下:
1.在 PL/SQL 块的定义部分定义异常情况:
<异常情况> EXCEPTION;
2. 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 PRAGMA EXCEPTION_INIT 语句:
PRAGMA EXCEPTION_INIT(< 异常情况>, < 错误代码>);
3. 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。

SQL>delete employees
where employee_id = 100
ORA-02292: 违反完整约束条件 (SCOTT.DEPT_MGR_FK) - 已找到子记录
因为自己表里有manager_id也指向自己表里的employee_id,而删除emp_id,因为manager_id指向,所以或无法删除

declare
e_deleteid_exception exception;
pragma exception_init(e_deleteid_exception,-2292);--将2292这个错和自己定义的异常名称关联起来
begin
delete employees
where employee_id = 100;
exception
when e_deleteid_exception then dbms_output.put_line('违反完整性约束异常!');
end;

用户自定义的异常处理

当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。 用户定义的异常错误是通过显式使
用 RAISE 语句来触发。当引发一个异常错误时,控制就转向到 EXCEPTION 块异常错误部分,执行错误处
理代码。
步骤如下:
1. 在 在 PL/SQL 块的定义部分定义异常情况:
< 异常情况> EXCEPTION;
2. RAISE < 异常情况>;
3.在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理
在非预言定义异常的基础上

declare
e_deleteid_exception exception;
pragma exception_init(e_deleteid_exception,-2292);
e_too_high_sal_exception exception; --自定义异常
v_sal employees.salary % type;
begin
delete from employees where employee_id = 100;
select salary into v_sal from employees where employee_id = 100;
if v_sal > 10000 then
raise e_too_high_sal_exception;   --抛出异常
end if;
exception
when e_too_high_sal_exception then dbms_output.put_line('工资太高了');
when e_deleteid_exception then dbms_output.put_line('违反完整性约束异常');
when others then dbms_output.put_line('其他异常');
end;

1-5为基础中的基础


六、存储函数和存储过程

过程和函数的唯一区别是函数总向调用者返回数据,而过程则不返回数据。
即存储函数(有返回值)、存储过程(无返回值)
-----存储函数:

创建函数
1.  建立内嵌函数
语法如下:
CREATE [OR REPLACE] FUNCTION function_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
RETURN return_type
{ IS | AS }
<类型.变量的说明>
BEGIN
FUNCTION_body
EXCEPTION
其它语句
END;

说明:
1) OR REPLACE 为可选. 有了它, 可以或者创建一个新函数或者替换相同名字的函数, 而不会出现冲突
2) 函数名后面是一个可选的参数列表, 其中包含 IN, OUT 或 IN OUT 标记. 参数之间用逗号隔开. IN 参数
标记表示传递给函数的值在该函数执行中不改变; OUT 标记表示一个值在函数中进行计算并通过该参
数传递 给调用语句; IN OUT 标记表示传递给函数的值可以变化并传递给调用语句. 若省略标记, 则参数
隐含为 IN 。
3) 因为函数需要返回一个值, 所以 RETURN 包含返回结果的数据类型。
--存储函数结构

create or replase function func_name (depart_id number, salary number)
return number --返回某部门所有的工资
is
--函数在使用过程中,需要声明的变量、记录类型record、游标cursor
begin
--函数的执行体
exception
--处理函数执行过程中的异常
end;

Hello,World演示

create or replace function HelloDemo
return varchar2
is
begin
return 'Hello,World';
end;

显示Function created,说明函数已经创建
调用函数
方式一:

begin
dbms_output.put_line(HelloDemo);
end;

方式二:

SQL>select HelloDemo from dual;

/*
create or replace function HelloDemo(v_world varchar2)
return varchar2
is
begin
return 'Hello'||v_world;
end;
*//*
select HelloDemo('Worrld') from dual
*/

获取系统时间函数

create or replace function get_sysdate
return date
is
v_date date;
begin
v_date := sysdate;
return v_date;
end;

select get_date from  dual;

定义两个数相加的函数

create or replace function add_func(v_num1 number,v_num2 number)
return number
is
v_sum number(10);--这里需要指定长度
begin
v_sum := v_num1 + v_num2;
return v_sum;
end;

select add_func(1,2) from dual;

获取指定部门的工资总和

create or replace function get_all_sal(dept_id number)
return number
is
v_sumsal number(20,2) := 0;

cursor salary_cursor is select salary from employees where department_id = dept_id;
begin
for c in salary_cursor loop
v_sumsal := v_sumsal + c.salary;
end loop;

return v_sumsal;
end;

关于OUT形的参数
因为函数只能有一个返回值,PL/SQL程序可以通过OUT参数实现多个返回值

获取指定部门的工资总和,人员总和

create or replace function get_all_sal(dept_id number,emp_num out number)--注意out这里
return number
is
v_sumsal number(20,2) := 0;

cursor salary_cursor is select salary from employees where department_id = dept_id;
begin
emp_num := 0;
for c in salary_cursor loop
v_sumsal := v_sumsal + c.salary;
emp_num := emp_num + 1;
end loop;

return v_sumsal;
end;

输出

declare
v_num number(3):=0;
begin
dbms_output.put_line(get_all_sal(80,v_num));
dbms_output.put_line(v_num); --v_num就是部门人数
end;

-----存储过程:
--定义存储过程,实现指定部门的工资(通过out参数),要求部门id和工资总数为参数

create or replace procedure get_sal(dept_id number,v_sal out number)--不需要返回值
is
cursor salary_cursor is select salary from employees where department_id = dept_id;
begin
v_sal := 0;
for c in salary_cursor loop
v_sal := v_sal + c.salary;
end loop;
dbms_output.put_line(v_sal);
end;

因为存储过程不需要返回值,所以可以把增删改的操作定义存储过程

七、触发器

触发器是许多关系数据库系统都提供的一项技术。在 ORACLE 系统里, 触发器类似过程和函数,都有
声明,执行和异常处理过程的 PL/SQL

语法:

CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
[OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...]
ON [schema.]table_name | [schema.]view_name
[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]
[FOR EACH ROW ]
[WHEN condition]
PL/SQL_BLOCK | CALL procedure_name;

其中:

BEFORE 和AFTER指出触发器的触发时序分别为前触发和后触发方式,前触发是在执行触发事件之前触发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的触发器。

FOR EACH ROW选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一个DML语句操走影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。当省略FOR EACH ROW 选项时,BEFORE 和AFTER 触发器为语句触发器,而INSTEAD OF 触发器则只能为行触发器。

REFERENCING 子句说明相关名称,在行触发器的PL/SQL块和WHEN 子句中可以使用相关名称参照当前的新、旧列值,默认的相关名称分别为OLD和NEW。触发器的PL/SQL块中应用相关名称时,必须在它们之前加冒号(:),但在WHEN子句中则不能加冒号。

WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查询语句,也不能调用PL/SQL 函数。WHEN 子句指定的触发约束条件只能用在BEFORE 和AFTER 行触发器中,不能用在INSTEAD OF 行触发器和其它类型的触发器中。

当一个基表被修改( INSERT, UPDATE, DELETE)时要执行的存储过程,执行时根据其所依附的基表改动而自动触发,因此与应用程序无关,用数据库触发器可以保证数据的一致性和完整性。

一个helloworld级别的触发器

create or replace trigger hello_trigger
after
update on employees
--for each row
begin
dbms_output.put_line('hello...');
--dbms_output.put_line('old.salary:'|| :OLD.salary||',new.salary'||:NEW.salary);
end;

然后执行:update employees set salary = salary + 1000;

DML触发器基本要点

l 触发时机:指定触发器的触发时间。如果指定为BEFORE,则表示在执行DML操作之前触发,以便防止某些错误操作发生或实现某些业务规则;如果指定为AFTER,则表示在执行DML操作之后触发,以便记录该操作或做某些事后处理。

l 触发事件:引起触发器被触发的事件,即DML操作(INSERT、UPDATE、DELETE)。既可以是单个触发事件,也可以是多个触发事件的组合(只能使用OR逻辑组合,不能使用AND逻辑组合)。

l 条件谓词:当在触发器中包含多个触发事件(INSERT、UPDATE、DELETE)的组合时,为了分别针对不同的事件进行不同的处理,需要使用ORACLE提供的如下条件谓词。

1)。INSERTING当触发事件是INSERT时,取值为TRUE,否则为FALSE。

2)。UPDATING [column_1,column_2,…,column_x]当触发事件是UPDATE 时,如果修改了column_x列,则取值为TRUE,否则为FALSE。其中column_x是可选的。

3)。DELETING当触发事件是DELETE时,则取值为TRUE,否则为FALSE。

解发对象:指定触发器是创建在哪个表、视图上。

l 触发类型:是语句级还是行级触发器。

l 触发条件:由WHEN子句指定一个逻辑表达式,只允许在行级触发器上指定触发条件,指定UPDATING后面的列的列表。

  问题:当触发器被触发时,要使用被插入、更新或删除的记录中的列值,有时要使用操作前、 后列的值.

  实现: :NEW 修饰符访问操作完成后列的值

   :OLD 修饰符访问操作完成前列的值

特性
INSERT
UPDATE
DELETE
OLD
NULL
实际值
实际值
NEW
实际值
实际值
NULL
6-7重点
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: