■ 游标
当在PL/SQL块中执行查询语句(SELECT)和数据操纵语句(DML)时,Oracle会为其分配一个上下文区(Context Area)
游标是指向上下文区的指针,它为应用提供了一种对具有多行数据查询结果集中的每一行数据分别进行单独处理的方法
隐含游标用于处理SELECT INTO和DML语句
显式游标则用于处理SELECT语句返回的多行数据
使用显式游标
定义游标 CURSOR cursor_name IS select_statement;
打开游标 OPEN cursor_name;
提取数据
FETCH cursor_name INTO variable1,variable2,...;
FETCH cursor_name bulk collect into collect1…;
关闭游标 CLOSE cursor_name;
游标属性使用格式为:游标名 + 属性名
%ISOPEN
用于确定游标是否已经打开。如果游标已经打开,则返回值为TRUE;否则返回FALSE
%FOUND
检查是否从结果集中提取到数据。如果提取到数据,则返回值为TRUE;否则返回FALSE
%NOTFOUND
与%FOUND属性恰好相反,如果提取到数据,则返回值为FALSE;否则返回TRUE
%ROWCOUNT
返回到当前行为止已经提取到的实际行数
显示游标示例
根据条件查询并输出部门信息
declare
v_dept dept%rowtype;
cursor dept_cursor is select * from dept where deptno>10; --定义游标
begin
open dept_cursor; --打开游标
loop
fetch dept_cursor into v_dept; --提取数据
exit when dept_cursor%notfound; --判断循环退出条件
dbms_output.put_line('编号:'||v_dept.deptno
||' 名称:'||v_dept.dname||' 地址:'||v_dept.loc);
end loop;
close dept_cursor; --关闭游标
end;
显式游标示例
用table结构提取游标数据
declare
type v_dept is table of dept%rowtype index by binary_integer;
cursor dept_cursor is select * from dept;
v_dept_table v_dept;
begin
open dept_cursor;
fetch dept_cursor bulk collect into v_dept_table;--将游标中的数据一次性地提取到表结构中。--
close dept_cursor;
for i in v_dept_table.first..v_dept_table.last loop--该表结构中数据存放方式不是从下标为零开始的。--
dbms_output.put_line(v_dept_table(i).deptno||'
'||v_dept_table(i).dname||' '||v_dept_table(i).loc);
end loop;
end;
自例:
/*显示游标*/
declare
v_stu student%rowtype;
cursor stu_cur is
select * from student where stu_id > 1007;
begin
open stu_cur;
loop
fetch stu_cur
into v_stu;
exit when stu_cur%notfound;
dbms_output.put_line(v_stu.stu_id || v_stu.stu_name ||
v_stu.class_id || v_stu.sex);
end loop;
close stu_cur;
end;
/*显示游标(全转)*/
declare
type v_student is table of student%rowtype index by binary_integer;
cursor stu_cur is
select * from student where stu_id > 1007;
v_stu v_student;
begin
open stu_cur;
fetch stu_cur bulk collect
into v_stu;
close stu_cur;
for i in v_stu.first .. v_stu.last loop
dbms_output.put_line(v_stu(i).stu_id || v_stu(i).stu_name || v_stu(i).class_id ||
v_stu(i).sex);
end loop;
end;
参数游标
查询指定部门的员工信息
declare
cursor emp_cursor(param_dept number) is--定义游标参数名及类型。--
select empno,ename from emp where deptno=param_dept;--把参数作为条件。--
emp_record emp_cursor%rowtype;--定义和游标集合相同的数据结构。--
begin
open emp_cursor(20);--在打开游标时传入参数值。--
loop
fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line(emp_record.empno||' '||emp_record.ename);
end loop;
close emp_cursor;
end;
游标for循环
使用游标for循环时,oracle隐含打开游标,提取数据并关闭游标。
示例:获取每个部门的名称
declare
cursor dept_cursor is select * from dept;
begin
for dept_row in dept_cursor loop--用for循环迭代游标数据。--
dbms_output.put_line('第'||dept_cursor%rowcount||--游标迭代的次数。--
'个部门:'||dept_row.dname);
end loop;
end;
当使用游标for循环时,可以直接使用子查询。
begin
for dept_row in(select deptno,dname from dept) loop--用子查询内容作为游标内容。--
dbms_output.put_line('第'||dept_rpwdeptno||
'个部门:'||dept_row.dname);
end loop;
end;
使用游标变量
使用显式游标时,需要在定义部分指定其所对应的静态SELECT语句
使用游标变量时,开发人员可以在打开游标变量时指定 其所对应的SELECT语句
TYPE ref_type_name IS REF CURSOR;
cursor_variable ref_type_name;
游标变量示例
显示部门编号为10的所有雇员姓名
declare
type emp_cursor_type is ref cursor;--定义游标变量。--
emp_cursor emp_cursor_type;
v_emp_row emp%rowtype;
begin
open emp_cursor for select * from emp where deptno=10;--打开游标时用 for定义游标内容。--
loop
fetch emp_cursor into v_emp_row;
exit when emp_cursor%notfound;
dbms_output.put_line('第'||emp_cursor%rowcount|| --用游标变量获取已经循环的行数。--
'个员工:'||v_emp_row.ename);
end loop;
close emp_cursor;
end;
隐含游标
当执行一条DML语句或者SELECT...INTO语句时,都会创建一个隐含游标
隐含游标的名称是SQL,不能对SQL游标显式执行OPEN、FETCH和CLOSE语句。
Oracle隐式地打开、提取,并总是自动地关闭SQL游标
隐式游标属性包括
SQL%FOUND、
SQL%NOTFOUND
SQL%ROWCOUNT
declare
v_empno number(4):=7369;
begin
delete from emp where empno=v_empno;
if sql%found then --判断隐含游标“sql"是否有影响的行数。--
dbms_output.put_line('存在该员工!');
else
dbms_output.put_line('不存在该员工!');
end if;
end;
触发器
触发器是指被隐含执行的存储过程,它可以使用PL/SQL进行开发
当发生特定事件(如修改表、创建对象、登录到数据库)时,Oracle会自动执行触发器的相应代码
触发器类型
DML触发器
在对数据库表进行DML操作时触发,并且可以对每行或者语句操作上进行触发。
替代触发器
oracle8专门为进行视图操作的一种触发器
系统触发器
对数据库系统事件进行触发,如启动、关闭等
触发器组成
触发事件
DML或DDL语句。
触发时间
是在触发事件发生之前(before)还是之后(after)触发
触发操作
使用PL/SQL块进行相应的数据库操作
触发对象
表、视图、模式、数据库
触发频率
触发器内定义的动作被执行的次数。
触发器由触发事件、触发条件和触发操作3个部分组成
编写触发器执行代码时,需要注意以下限制
触发器不接受参数
一个表上最多可有12个触发器,但同一时间、同一事件、同一类型的触发器只能有一个。并各触发器之间不能有矛盾。
一个表上的触发器越多,该表上的DML操作的性能影响就越大
触发器代码的大小不能超过32K。如需要大量的代码创建触发器,则首先创建过程,然后在触发器中使用CALL语句调用过程
触发器代码只能包含SELECT、INSERT、UPDATE和DELETE语句,
不能包含DDL语句(CREATE、ALTER和DROP)和事务控制语句(COMMIT、ROLLBACK和SAVEPOINT)
语句触发器
语句触发器是指当执行DML语句时被隐含执行的触发器
如果在表上针对某种DML操作创建了语句触发器,则当执行DML操作时会自动地执行触发器的相应代码
为了审计DML操作,或者确保DML操作安全执行时,可以使用语句触发器
参数 | 说明 |
trigger_name | 指定触发器名 |
BEFORE | AFTER | 指定触发时机(BEFORE或AFTER) |
event | 指定触发事件(INSERT、UPDATTE和DELETE) |
table_name | 指定DML操作所对应的表名 |
FOR EACH ROW | 说明触发器为行触发器 |
REFERENCING | 说明相关名称 |
WHEN | 说明触发约束条件 |
创建触发器
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}]
[FOR EACH ROW ][WHEN condition]PL/SQL_BLOCK | CALL procedure_name;
触发语句与伪记录变量的值
触发语句 | :old | :new |
Insert | 将要插入的数据 | |
Update | 更新以前该行的值 | 更新后的值 |
delete | 删除以前该行的值 |
触发器触发次序
1. 执行 BEFORE语句级触发器;
2. 对与受语句影响的每一行:
执行 BEFORE行级触发器
执行 DML语句
执行 AFTER行级触发器
3. 执行 AFTER语句级触发器
创建DML触发器
行级触发器
for each row
:old 修改前的该行记录
:new 修改后的该行记录
如果修改的是部门编号为30的员工工资,则工资不能降低
create or replace trigger trig_update_sal
before update of sal,comm
or delete on emp
for each row
when(old.deptno=30) --old:删除或修改前的数据。--
begin
case
when updating('sal') then
if :new.sal<:old.sal then --:old:删除或修改前的数据。:new:修改后的数据--
raise_application_error(-20002,'部门30的人员工资不能降');
end if;
when updating('comm') then
if :new.comm<:old.comm then
raise_application_error(-20001,'部门30的奖金不能降低!');
end if;
when deleting then
raise_application_error(-20003,'不能删除部门30的员工');
end case;
end;
创建AFTER语句触发器
如果指定了AFTER关键字,则表示在执行DML操作之后触发触发器
利用行触发器实现级联更新。在修改了主表dept中的deptno之后(AFTER),级联的、自动的更新子表emp中原来在该部门的deptno。
create or replace trigger tri_casupdate
after update of deptno on dept --在部门表的deptno字段建立afterupdate触发器。--
for each row
begin
dbms_output.PUT_LINE('旧的deptno值是:'||:old.deptno);
dbms_output.PUT_LINE('新的deptno值是:'||:new.deptno);
update emp set deptno=:new.deptno where deptno=:old.deptno;
end;
在触发器中调用存储过程
有时业务逻辑过于复杂,触发器内容有限(32K),只能借助于存储过程
在删除dept表中记录时,将原有的记录保存到一个回收表delDept中
创建delDept表
create or replace trigger tri_pro
after delete on dept
for each row
begin
pro_addOldDept(:old.deptno,:old.dname,:old.loc);--触发器中调用存储过程--
end;
创建INSTEAD OF 触发器
为了在不能执行DML操作的复杂视图上执行DML操作,必须基于视图创建INSTEAD OF触发器
创建INSTEAD OF触发器时需要注意以下几点
INSTEAD OF选项只适用于视图
当基于视图创建触发器时,不能指定BEFORE和AFTER选项
CREATE [OR REPLACE] TRIGGER trigger_name
INSTEAD OF
{INSERT | DELETE | UPDATE [OF column [, column …]]}
[OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...]
ON [schema.] view_name --只能定义在视图上
[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]
[FOR EACH ROW ] --因为INSTEAD OF触发器只能在行级上触发,所以没有必要指定
[WHEN condition]
PL/SQL_block | CALL procedure_name;
create or replace view emp_view as
select deptno,count(*) total_employeer,sum(sal) total_sal
from emp group by deptno;
创建instead_of触发器
create or replace trigger emp_view_del
instead of delete on emp_view for each row
begin
delete from emp where deptno=:old.deptno;
end;
创建系统事件触发器
系统事件触发器是指基于Oracle系统事件所创建的触发器
创建系统事件触发器时,应用开发人员经常需要使用事件属性函数
事件属性函数 | 功能 |
ora_client_ip_address | 返回客户端的IP地址 |
ora_database_name | 返回当前数据库名 |
ora_dict_obj_name | 返回DDL操作所对应的数据库对象名 |
ora_dict_obj_owner | 返回DDL操作所对应的对象的所有者名 |
ora_dict_obj_type | 返回DDL操作对应的数据库对象的类型 |
ora_instance_num | 返回例程号 |
ora_is_alter_column(column_name IN VARCHAR2) | 检测特定列是否被修改 |
ora_is_drop_column(column_name IN VARCHAR2) | 检测特定列是否被删除 |
ora_login_user | 返回登录用户名 |
ora_sysevent | 返回触发触发器的系统事件名 |
创建系统事件触发器
创建登录和退出触发器
为了记载用户的登录和退出事件,可以分别创建登录和退出触发器
创建用于存放登录和退出信息的表
创建登录和退出触发器
用户登录数据库或断开与数据库的连接时执行相应的触发器代码
create table log_event(
username varchar2(20),
ipAddress varchar2(20),
logonTime timestamp,
logoffTime timestamp
);
create or replace trigger logoff_trigger
before logoff on database
begin
insert into log_event(username,ipaddress,logoffTime)
values(ora_login_user,ora_client_ip_address,sysdate);
end;
create or replace trigger logon_trigger
after logon on database
begin
insert into log_event(username,ipaddress,logonTime)
values(ora_login_user,ora_client_ip_address,sysdate);
end;
管理触发器
显示触发器信息
通过查询数据字典视图USER_TRIGGERS,可以显示当前用户所包含的所有触发器信息
禁用或启用触发器
重新编译触发器
当使用ALTER TABLE命令修改表的结构时,会使触发器变为无效状态
为了使触发器继续生效,需要重新编译触发器
删除触发器
合理的是数据库设计
建立优化索引
避免在索引列上使用计算
WHERE子句中的连接顺序1
减少对表的查询
合理进行数据库的设计
很多的数据库DBA都把系统优化的焦点放在数据库参数的调整、碎片的清理等方面。但实际上最重要的莫过于在数据库设计的初期就着眼于数据库的优化,在这个阶段就要根据设计准则规范操作,设计出高效合理的数据库模型。数据库的设计通常分两步,即逻辑设计和物理设计。
在数据库逻辑设计过程中,按照关系数据库的规范化要求,为了保证数据库的一致性和完整性,设计人员往往会设计过多的表间关联,以尽可能地降低数据冗余。表间关联是一种强制性措施(触发器实现),建立后,对父表和子表的插入、更新、删除操作均要占用系统的开销。
另外,在进行数据查询时也增加了表间连接查询的操作,从而使数据库的性能大为降低。如果此时不考虑优化的问题,那么就算是编写的SQL语句优化做的再好,也是不可能提高数据库的性能。
因此物理设计需折中考虑,根据业务规则,确定对关联表的数据量大小、数据项的访问频度,对比较频繁的数据表关联查询应适当提高数据冗余设计。以下方法经实践验证往往能提高性能。
1)常用的计算字段(如总计、最大值等)和通过复杂计算的到的字段可以考虑存储到数据库表中。这点相当重要,因为很多情况下,计算这些值的开销往往大于访问的开销。
2)把频繁被访问的数据同较少被访问的数据分开存储。
建立优化索引
当要改进查询的性能时,索引是最便捷的,常常也是最好的技术。多数新应用程序的存取速度问题是由于缺少合适的索引。使用索引可以使性能发生巨大的变化,而且确定需要什么样的索引也相对简单,合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
a) 有大量重复值、且经常有范围查询(between, > ,< ,> =,< =)和order by、group by发生的列,可考虑建立群集索引;
b) 经常同时存取多列,且每列都含有重复值可考虑建立组合索引;
c)组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。
对待索引的建立要特别谨慎,因为索引要占用磁盘空间,如果索引同表本身的大小差不多,那就不能从索引得到多少好处。索引还要对表的更新、插入和删除的性能产生影响,除了这几点之外,索引是改进查询性能的最简单、最有效的方法,尤其是当您总是对几个关键字字段进行搜索时,这种方法最合适。
索引建立要花费大量时间,可以使用ALTER INDEX REBUILD 来创建新索引,这比其删除和重建索引要迅速得多。如果用户的数据已经排序,在构建索引时使用NOSORT选项,如:
ALTER INDEX emp_primary_key REBUILD;
CREATE INDEX myindex ON emp(ename) NOSORT;
避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。
举例:
低效:
SELECT …
FROM DEPT
WHERE SAL * 12 > 25000;
高效:
SELECT 。
FROM DEPT
WHERE SAL > 25000/12;
注意组合索引的顺序
由于列的顺序不同而导致索引的作用效果不同。
例如:(AGE,ADDRESS)顺序的结合索引。
WHERE AGE=28 AND ADDRESS=’BEI JING’
&组合索引有效
WHERE AGE=28
&组合索引有效
WHERE ADDRESS=’BEI JING’
&组合索引无效
选择最有效率的表名顺序
ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表driving table)将被最先处理。在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。
当ORACLE处理多个表时,会运用排序及合并的方式连接它们。
首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序,
然后扫描第二个表(FROM子句中最后第二个表),
最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
WHERE子句中的连接顺序
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。
例如:(低效,执行时间156。3秒)
SELECT …
FROM EMP E
WHERE SAL > 50000
AND JOB = ‘MANAGER’
AND 25 < (SELECT COUNT(*) FROM EMP
WHERE MGR=E.EMPNO);
例如:(高效,执行时间10。6秒)
SELECT …
FROM EMP E
WHERE 25 < (SELECT COUNT(*) FROM EMP
WHERE MGR=E.EMPNO)
AND SAL > 50000
AND JOB = ‘MANAGER’;
SELECT子句中避免使用*
当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用‘*’ 是一个方便的方法。
不幸的是,这是一个非常低效的方法。
实际上,ORACLE在解析的过程中, 会将’*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
当执行每条SQL语句时, ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量, 读数据块等等。 由此可见, 减少访问数据库的次数, 就能实际上减少ORACLE的工作量。
例如: 以下有三种方法可以检索出雇员号等于0342或0291的职员。
方法1 (最低效)
SELECT EMP_NAME , SALARY , GRADE
FROM EMP
WHERE EMP_NO = 342;
SELECT EMP_NAME , SALARY , GRADE
FROM EMP
WHERE EMP_NO = 291;
方法2 (次低效)
DECLARE
CURSOR C1 (E_NO NUMBER) IS
SELECT EMP_NAME,SALARY,GRADE
FROM EMP
WHERE EMP_NO = E_NO; BEGIN
OPEN C1(342);
FETCH C1 INTO … … … … ;
… …
OPEN C1(291);
FETCH C1 INTO … … … … ;
CLOSE C1;
END;
方法3 (高效)
SELECT A.EMP_NAME , A.SALARY , A.GRADE,
B.EMP_NAME , B.SALARY , B.GRADE
FROM EMP A,EMP B
WHERE A.EMP_NO = 342
AND B.EMP_NO = 291;
使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表。
例如:
SELECT COUNT(*),SUM(SAL)
FROM EMP
WHERE DEPT_NO = 0020
AND ENAME LIKE ‘SMITH%’;
SELECT COUNT(*),SUM(SAL)
FROM EMP
WHERE DEPT_NO = 0030
AND ENAME LIKE ‘SMITH%’;
你可以用DECODE函数高效地得到相同结果
SELECT COUNT(DECODE(DEPT_NO,0020,’X’,NULL)) D0020_COUNT,
COUNT(DECODE(DEPT_NO,0030,’X’,NULL)) D0030_COUNT,
SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL,
SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL
FROM EMP WHERE ENAME LIKE ‘SMITH%’;
类似的,DECODE函数也可以运用于GROUP BY 和ORDER BY子句中
最高效的删除重复记录方法( 因为使用了ROWID)
DELETE FROM EMP E
WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X
WHERE X.EMP_NO = E.EMP_NO);
用TRUNCATE替代DELETE
当删除表中的记录时,在通常情况下, 回滚段(rollback segments) 用来存放可以被恢复的信息。
如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)
而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息。当命令运行后,数据不能被恢复。因此很少的资源被调用,执行时间也会很短。
TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML
尽量多使用COMMIT
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:
COMMIT所释放的资源: 减少对表的查询
a. 回滚段上用于恢复数据的信息。
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
用Where子句替换HAVING子句
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤。 这个处理需要排序,总计等操作。
如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。
例如:
低效:
SELECT REGION,AVG(LOG_SIZE)
FROM LOCATION
GROUP BY REGIONHAVING REGION != ‘SYDNEY’
AND REGION != ‘PERTH’
高效:
SELECT REGION,AVG(LOG_SIZE)
FROM LOCATION
WHERE REGION != ‘SYDNEY’ AND REGION != ‘PERTH’
GROUP BY REGION
HAVING 中的条件一般用于对一些集合函数的比较,如COUNT() 等等。 除此而外,一般的条件应该写在WHERE子句中)
减少对表的查询
在含有子查询的SQL语句中,要特别注意减少对表的查询。
例如:
低效:
SELECT TAB_NAME
FROM TABLES
WHERE TAB_NAME = ( SELECT TAB_NAME
FROM TAB_COLUMNS
WHERE VERSION = 604)
AND DB_VER= ( SELECT DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)
高效
SELECT TAB_NAME
FROM TABLES
WHERE (TAB_NAME,DB_VER) =
( SELECT TAB_NAME,DB_VER)
FROM TAB_COLUMNS
WHERE VERSION = 604)
Update 多个Column 例子:
低效:
UPDATE EMP
SET EMP_CAT=
(SELECT MAX(CATEGORY) FROM EMP_CATEGORIES), SAL_RANGE = (SELECT MAX(SAL_RANGE)
FROM EMP_CATEGORIES)
WHERE EMP_DEPT = 0020;
高效:
UPDATE EMP
SET (EMP_CAT, SAL_RANGE) =
(SELECT MAX(CATEGORY) , MAX(SAL_RANGE)
FROM EMP_CATEGORIES)
WHERE EMP_DEPT = 0020;
使用表的别名(Alias)
(Column歧义指SQL的是由于SQL中不同的表具有相同的Column名,当语句中出现这个Column时,SQL解析器无法判断这个Column的归属)
分离表和索引
将你的表和索引建立在不同的表空间内(TABLESPACES)。 决不要将不属于ORACLE内部系统的对象存放到SYSTEM表空间里。 同时,确保数据表空间和索引表空间置于不同的硬盘控制卡控制的硬盘上。