■ 游标
显示游标示例
根据条件查询并输出部门信息
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;
显式游标示例
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;
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;
游标变量示例
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;
触发器
参数 |
说明 |
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;
创建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;
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;
事件属性函数 |
功能 |
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);
SELECT …
FROM EMP E
WHERE 25 < (SELECT COUNT(*) FROM EMP
WHERE MGR=E.EMPNO)
AND SAL > 50000
AND JOB = ‘MANAGER’;
当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用‘*’ 是一个方便的方法。
不幸的是,这是一个非常低效的方法。
实际上,ORACLE在解析的过程中, 会将’*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
方法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子句中
DELETE FROM EMP E
WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X
WHERE X.EMP_NO = E.EMP_NO);
用TRUNCATE替代DELETE
尽量多使用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子句中)
例如:
低效:
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)