PL/SQL 基础培训 北京神州数码思特奇信息技术股份有限公司 研究院 www.si-tech.com.cn Oracle 数据库开发——PL/SQL PL/SQL基础 – – – – – – – – – PL/SQL语言的简介 数据类型 程序结构 流程控制 异常处理 过程与函数 游标 触发器 SQL优化初步 PL/SQL基础 SQL语句、PL/SQL块和SQL*Plus命令之间的区别: 1)SQL语句是以数据库为操作对象的语言,主要包括数据定义语言DDL 、数据操纵语言DML和数据控制语言DCL以及数据存储语言DSL。当 输入SQL语句后,SQL*Plus将其保存在内部缓冲区中。 2)PL/SQL块同样是以数据库中的数据为操作对象。但由于SQL不具备 过程控制功能,所以,为了能够与其他语言一样具备面向过程的处理 功能,在SQL中加入了诸如循环、选择等面向过程的处理功能,由此 形成了PL/SQL。所有PL/SQL语句的解释均由PL/SQL引擎来完成。 使用PL/SQL块可编写过程、触发器和包等数据库永久对象。 3)SQL*Plus命令主要用来格式化查询结果、设置选择、编辑及存储 SQL命令、以设置查询结果的显示格式,并且可以设置环境选项。 PL/SQL基础 1、PL/SQL:PL/SQL是一种过程化编程语言,运行于服务器端的编程语 言,PL/SQL和SQL作无缝连接,是和C、Java一样关注于实现的细节 ,因此可以实现复杂的业务逻辑处理。PL/SQL是对SQL的一种扩展, 把SQL语言的灵活性和过程化结构融合在一起。 2、PL/SQL改善性能: • PL/SQL是以整个语句块的形式发送给服务器的,降低了网络负载,从 而提高性能。因为SQL语句是以语句为单位进行发送的,在网络环境 下会占用大量的服务器时间,同时导致网络拥挤。 如图: Oracle数据库服务器 SQL SQL SQL 客户端应用程序 使用SQL Oracle数据库服务器 SQL …… SQL 客户端应用程序 PL/SQL基础 PL/SQL结构 PL/SQL的基本结构是块,所有的PL/SQL程序都是由块组成的, PL/SQL的块结构如下: declare /*声明部分,包括变量、常量、游标、用户定义的异常的定义等*/ begin /*可执行的部分,包括SQL语句和过程化语句,是程序的主体*/ exception /*异常处理部分,包括异常处理语句*/ end /*块结束语句*/ PL/SQL基础 PL/SQL数据类型 数字型 用来存储整数或实数。NUMBER、BINARY_INTEGER、PLS_INTEGER NUMBER-----存储整数和浮点数 BINARY_INTEGER-----存储带符号的整数值,溢出时不发生错误 PLS_INTEGER-----存储带符号的整数值,溢出时发生错误 例如: V_NUM NUMBER(5); v_binarynum binary_integer; PL/SQL基础 • 字符型:用类存储字符串或字符数据。包括 VARCHAR2 、CHAR、LONG、NCHAR、NVARCHAR2。 VARCHAR2-----存储可变长度的字符串 CHAR-----存储固定长度的字符串 LONG-----存储可变长度的字符串,其最大长度是32760字节。 NCHAR 和 NVARCHAR2-----NLS 字 符 类 型 用 于 存 储 来 自 不 同 于 PL/SQL语言的字符集中的字符集。 例如: V_CHAR VARCHAR2(20); PL/SQL基础 • 日期型:用来存储日期和时间信息,包括世纪、年、 月、天、小时、分钟和秒。唯一的类型为DATE。 例如: V_DATE DATE; • 布尔型:布尔型的类型为 BOOLEAN。布尔变量在 PL/SQL控制结构中使用,BOOLEAN变量只能存储 TRUE、FALSE、NULL值。 例如: V_BOOLEAN BOOLEAN; PL/SQL基础 • date、timestamp类型 • 示例演示介绍 PL/SQL基础 • 原始型:用来存储二进制数据。包括RAW、LONG RAW RAW-----存储定长的二进制数据。类似于 CHAR类型, 但不在字符集之间进行转换。 LONG RAW-----与LONG 类似,最大长度为32760字节 ,但不在字符集之间进行转换。 例如: V_LONG LONG; PL/SQL基础 空值处理 NULL+〈数字〉=NULL (空值加数字仍是空值) NULL> 〈数字〉=NULL (空值与数字比较,结果仍是空值) NULL||‘字符串’= ‘字符串’ (空值与字符串进行连接运算,结 果为原字符串) 判断一个变量的值是否为NULL的正确写法为: if my_var is null then .... end if; 错误写法为: if my_var = null then .... end if; PL/SQL基础 使用NULL值进行比较时,注意: 例: a:=5; b:=null; if a<>b then .... end if; 例: x:=null; y:=null; if x=y then .... end if; PL/SQL基础 变量的声明:除了满足SQL基本命名规则,变量还要以v_开头,常量以 c_开头,声明变量或常量的语法如下: 标识符 [constant] datatype [not null][:=|default expr]; 例如: declare v_ch char(20); c_ch constant char(10) not null := 'World!'; begin v_ch := 'Hello'; dbms_output.put_line(v_ch); dbms_output.put_line(c_ch); exception when others then null; end; 注意:变量名和常量名不能与Oracle数据库中表名或字段名相同 ,变量如果没有初始值,默认是null。 PL/SQL基础 例如: declare c_pi constant number(3,2) default 3.14; v_area number(8,2); v_r number(2); begin v_r:=2; v_area:=c_pi*v_r*v_r; dbms_output.put_line('圆的面积是: '||v_area); end; PL/SQL基础 例如: declare v_i number := 5; v_ch char(20) := 'How are you'; v_today char(15) := to_char(sysdate,'yyyy/mm/dd'); v_flag boolean:=true; begin dbms_output.put_line(v_i||' '||v_ch||' '||v_today); end; PL/SQL基础 例如: declare v_avgprice titles.price%type; v_date char(20); begin v_date:=to_char(sysdate,'yyyy/mm/dd'); select avg(price) into v_avgprice from titles; dbms_output.put_line(v_avgprice); dbms_output.put_line(v_date); end; PL/SQL基础 set serveroutput on --set serveroutput off declare v_empno emp.empno%Type; /*声明变量v_empno, %type: 使该变量的类型与emp表中的empno类型相同*/ v_emprecord emp%Rowtype; /*声明变量v_emprecord, %rowtype: 使该变量的类型与emp表中的整行相同*/ begin Select * Into v_emprecord From emp Where empno=&v_empno; dbms_output.put_line('雇员编号 ‘ || v_emprecord.empno); dbms_output.put_line('雇员姓名 ‘ || v_emprecord.ename); dbms_output.put_line('入职日期 ‘ || v_emprecord.hiredate); dbms_output.put_line('职位 ‘ || v_emprecord.job); dbms_output.put_line('管理员编号 ‘ || v_emprecord.mgr); dbms_output.put_line('工资 ‘ || v_emprecord.sal); dbms_output.put_line('奖金 ‘ || v_emprecord.comm); dbms_output.put_line('部门编号 ‘ || v_emprecord.deptno); end; / PL/SQL基础 PL/SQL 的记录类型 把逻辑相关的数据作为一个单元存储起来,在declare 段中定义 record类型数据,使某一变量使用该record型数据. 定义方法: TYPE r_record is RECORD ( v_name emp.ename%TYPE, v_job emp.job%TYPE, v_sal emp.sal%TYPE ); 变量定义 变量使用 r_emp r_record; SELECT ename,job,sal INTO r_emp FROM emp WHERE empno=7934; 则,r_emp.v_ename,r_emp.v_job,r_emp.v_sal 已有值; 给变量赋值: r_employee r_record; r_employee.v_ename :=‘JACK’; r_employee.v_job :=‘CLERK’; r_employee.v_sal := 890.98; PL/SQL基础 PL/SQL 的记录类型 declare type rec_emp is record PL/SQL的复合类型主要包括record记录类型。record复合数据类型在使 ( 用前必须定义,类似C语言的结构体类型。也可以使用rowtype来定义。 v_empno emp.empno%type, 例1: v_ename emp.ename%type, v_sal emp.sal%type ); v_empinfo rec_emp; declare type recTypeStudent is record( begin sname varchar2(10), age declare number(2) select empno,ename,sal ); v_emp emp%rowtype; into v_empinfo from emp v_recStu recTypeStudent; begin where empno=7369; select * into v_emp dbms_output.put_line(v_empinfo.v_empno||' begin from emp '||v_empinfo.v_ename); where empno=7369; v_recStu.sname := 'zhang';end; dbms_output.put_line(v_emp.empno); v_recStu.age := 20; end; || ' ' || v_recStu.age ); dbms_output.put_line ( v_recStu.sname end; 例2:定义一个记录类型,接收7369号员工的信息并打印。(独立实现) PL/SQL基础 TABLE 类型数据 PL/SQL中的表(table)类型类似于C语言中的结构类型数组. 定义方法:TYPE table_emp IS TABLE OF emp . ename %TYPE INDEX BY BINARY_INTEGER; 一个PL/SQL表有两个列,(key, value) 1. key 列类型即是BINARY_INTEGER 2. value类型则是所定义的数据类型. table类型使用: 定义变量 my_name为 table_emp 类型,则可以使用变量 my_name , 也可以在SQL语句中使用 table类型变量. my_name table_emp; my_name(0) :=‘SCOTT’; my_name(1) :=‘SMITH’; my_name(2) :=‘SUSAN’; SELECT ename INTO my_name(10) FROM emp WHERE empno = 7934; PL/SQL基础 用法演示 PL/SQL基础 PL/SQL中数据类型之间的转换:使用to_char、to_date、to_number来进行显性 的数据类型转换。 例如: declare v_i number; v_j number; begin v_i:=1; v_j:=2; dbms_output.put_line(concat('i+j=',to_char(v_i+v_j))); end; Oracle中的表达式和运算符: (1)Oracle的过程语句中使用的函数: 1)单行数值函数:mod、round、trunc、ceil、floor。 2)单行字符函数:chr、concat、initcap、length、lower、lpad、rpad、ltrim、 replace、rtrim、substr、trim、upper 3)日期函数:add_months、last_day、months_between、next_day、round、 sysdate、trunc 4)转换函数:to_char、to_number、to_date (2)在oracle中不能直接使用的函数:decode、组函数 PL/SQL基础 函数用法: 1.instr函数 • 格式为 INSTR(源字符串, 要查找的字符串, 从第几个字符开始, 要找到第几个匹配的序号) 返回找到的位置,如果找不到则返回0. • 例如:INSTR('CORPORATE FLOOR','OR', 3, 2)中,源字符串为 'CORPORATE FLOOR', 在字符串中查找'OR',从第三个字符位置开 始查找"OR",取第三个字后第2个匹配项的位置。 • 默认查找顺序为从左到右。当起始位置为负数的时候,从右边开始查 找。 • 所以SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "aaa" FROM DUAL的显示结果是 – Instring —————— 14 PL/SQL基础 函数用法: 2.substr函数 • 取得字符串中指定起始位置和长度的字符串 – substr( string, start_position, [ length ] ) 如: substr(‘This is a test’, 6, 2) substr(‘This is a test’, 6) substr(‘TechOnTheNet’, -3, 3) substr(‘TechOnTheNet’, -6, 3) would return ‘is’ would return ‘is a test’ would return ‘Net’ would return ‘The’ select substr('Thisisatest', -4, 2) value from dual PL/SQL基础 综合应用: 1. • SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "Instring" FROM DUAL --INSTR(源字符串, 目标字符串, 起始位置, 匹配序号) • SELECT INSTR('CORPORATE FLOOR','OR', 3, 2) "Instring" FROM DUAL • SELECT INSTR('32.8,63.5',',', 1, 1) "Instring" FROM DUAL • SELECT SUBSTR('32.8,63.5',INSTR('32.8,63.5',',', 1, 1)+1) "INSTRING" FROM DUAL • SELECT SUBSTR('32.8,63.5',1,INSTR('32.8,63.5',',', 1, 1)-1) "INSTRING" FROM DUAL PL/SQL基础 综合应用: 2. DECLARE -- LOCAL VARIABLES HERE T VARCHAR2(2000); S VARCHAR2(2000); NUM INTEGER; I INTEGER; POS INTEGER; BEGIN -- TEST STATEMENTS HERE T := '12.3,23.0;45.6,54.2;32.8,63.5;'; SELECT LENGTH(T) - LENGTH(REPLACE(T, ';', '')) INTO NUM FROM DUAL; DBMS_OUTPUT.PUT_LINE('NUM:' || NUM); POS := 0; FOR I IN 1 .. NUM LOOP DBMS_OUTPUT.PUT_LINE('I:' || I); DBMS_OUTPUT.PUT_LINE('POS:' || POS); DBMS_OUTPUT.PUT_LINE('==:' || INSTR(T, ';', 1, I)); DBMS_OUTPUT.PUT_LINE('INSTR:' || SUBSTR(T, POS + 1, INSTR(T, ';', 1, I) - 1)); POS := INSTR(T, ';', 1, I); END LOOP; END; PL/SQL基础 函数用法: 3.decode函数 • 格式 – decode(条件,值1,翻译值1,值2,翻译值2,...值n,翻译值n,缺省值) Eg1: 假设我们想给职员加工资,其标准是:工资在8000元以下的将加 20%;工资在8000元以上的加15% select decode(sign(salary - 8000),1,salary*1.15,-1,salary*1.2) as salary from employee PL/SQL基础 函数用法: 4. substr与substrb函数 substr是按照字来算的,而substrb()是按照字节来计算。 – SQL> select substr('今天是个好日子',3,5) from dual; ---------是个好日子 – SQL> select substrb(‘今天是个好日子’,3,5) from dual; ----天是 length与lengthb 长度计算函数 • select length('你好') from dual ----output:2 • select lengthb('你好') from dual ----output :4 Instr与Instrb 字符串查找函数 instr(原字符串,查的字符串,起始位置, 第几个匹配) 返回字符串位置,找不到返回0 . • select instr('日日花前长病酒','花前',1,1) from dual ----output:3 • select instrb('日日花前长病酒','花前',1,1) from dual ----output:5 PL/SQL基础 Oracle中可以使用的操作符: set serveroutput on 1)算术操作符:+、-、*、/、**(乘方) declare 2)关系运算符:<、<=、>、>=、=、<>、!= v_time number(2); 3)其他的比较运算符:is null、like、between…and、in begin 4)逻辑运算符:and、or、not v_time := to_char(sysdate,'hh24'); 5)其他操作符||、:= if v_time >= 6 and v_time <= 8 then PL/SQL中的控制结构: (1)条件语句: dbms_output.put_line('起床。'); elsif v_time > 8 and v_time < 17 then if 条件1 then 语句体; elsif 条件2 then 语句体; ………. else 语句体; endif; dbms_output.put_line('工作'); elsif v_time >= 18 and v_time <= 22 then dbms_output.put_line('下班'); else dbms_output.put_line('睡觉'); end if; end; 例:用上面的结构,写一个分时问候的程序 PL/SQL基础 (2)条件语句的嵌套使用:在if或if…else中可以嵌套if或if…else 例1:取出雇员ID为7369的薪水,如果<1200,则输出‘low’,如果<2000, 则输出'middle',否则输出'high' 例2:判断当前年分是否是闰年。 declare v_year number; begin v_year : =to_char(sysdate,'yyyy'); if mod(v_year,4)=0 and mod(v_year,100)!=0 or mod(v_year,400)=0 then dbms_output.put_line('The year is '||v_year); dbms_output.put_line('The year is leap year'); else dbms_output.put_line('The year is not leap year'); end if; end; 例2:打印今年当前月份的天数。(独立实现) declare v_year number; v_days number; if v_leapyear then v_month number; if (v_month=1 or v_month=3 or v_month=5 or v_month=7 or v_leapyear boolean; v_month=8 or v_month=10 or v_month=12) then v_days:=31; elsif (v_month=4 or v_month=6 or v_month=8 or v_month=10) begin then v_year:=to_char(sysdate,'yyyy'); v_days:=30; else v_month:=to_char(sysdate,'mm'); v_days:=29; end if; and mod(v_year,100)!=0 or v_leapyear:=( mod(v_year,4)=0 mod(v_year,400)=0); else if (v_month=1 or v_month=3 or v_month=5 or v_month=7 or v_month=8 or v_month=10 or v_month=12) then v_days:=31; …….. elsif (v_month=4 or v_month=6 or v_month=8 or v_month=10 )then v_days:=30; else v_days:=28; dbms_output.put_line(v_days); end if; end if; end; PL/SQL基础 (3) PL/SQL: CASE 结构 CASE WHEN 条件表达式1 THEN 语句段1 WHEN 条件表达式2 THEN 语句段2 …… ELSE 语句段N END CASE; PL/SQL基础 示例:CASE 结构 declare v_grade char :='A'; begin case v_grade when 'A' then DBMS_OUTPUT.put_line('Excellent'); when 'B' then DBMS_OUTPUT.put_line('Very good'); when 'C' then DBMS_OUTPUT.put_line('Good'); else DBMS_OUTPUT.put_line('No such grade'); end case; end; PL/SQL基础 declare declare v_x number; v_x number; begin 循环结构: v_sum number; v_x:=1; 1)简单循环:循环体至少循环一次,loop语法如下: loop begin dbms_output.put_line(v_x); loop v_sum:=0; v_x:=v_x+1; 语句体; v_x:=1; if v_x>20 then [exit;] loop exit; end loop; end if; v_sum:=v_sum+v_x; end loop; 例1:打印1,2,3…20; v_x:=v_x+1; end; 例2:求1+3+5+…25; if v_x>=25 then 注意:也可以使用exit when来结束循环。 例3:将上面的两个例子用exit when改写。 exit; end if; end loop; dbms_output.put_line(v_sum); end; PL/SQL基础 2)while 循环语法: while 条件 loop 语句体; end loop; declare v_x number; v_i number; begin v_x:=1; v_i:=0; while v_x<=100 loop dbms_output.put(v_x||' '); v_x:=v_x+1; v_i:=v_i+1; if mod(v_i,10)=0 then dbms_output.put_line(''); end if; end loop; end; PL/SQL基础 2) for 循环语法: 例1: declare v_i number:=1; begin for counter in 1..10 loop dbms_output.put_line(v_i); v_i:=v_i+1; end loop; end; PL/SQL基础 2) for 循环语法: 例2: set serveroutput on declare v_x number; begin v_x:=0; for counter in 1..10 loop dbms_output.put_line(v_x); v_x:=v_x+1; end loop; dbms_output.put_line(v_x); end; PL/SQL基础 declare 练习:v_empno number(4); 例1:如想获得7369号员工的信息,并打印出来。 v_ename varchar2(10); 例2:打印bu1032图书的基本信息和销售信息,包括书号、书名、类型、价 v_job varchar2(9); 格、销售日期和销售量、及出版社名称和作者信息。(独立实现)。 v_mgr number(4); v_hiredate date; v_sal number(7,2); v_comm number(7,2); v_deptno number(2); begin select * into v_empno,v_ename,v_job,v_mgr,v_hiredate,v_sal,v_comm,v_deptno from emp where empno=7369; dbms_output.put_line(v_empno||' '||v_ename||' '||v_job||' '||v_hiredate||' '||v_sal||' '||v_deptno); exception when others then null; end; PL/SQL基础 INSERT语句的使用 Declare v_empno EMP . empno%TYPE :=1234; v_ename EMP . ename%TYPE :=‘SCOTT’; v_job VARCHAR2(15) :=‘MANAGER’; v_deptno EMP . deptno%TYPE :=20; v_sal NUMBER(7,2) :=890.50; Begin INSERT INTO emp(empno, ename, job, hiredate, sal, deptno) VALUES(v_empno, v_ename, v_job, SYSDATE, v_sal, v_deptno); dbms_output.put_line(sql%rowcount || "条记录被影响!"); END; / 注意:非空(NOT NULL) 必须有值 PL/SQL基础 DELETE 语句的使用 Declare v_empno EMP.empno%TYPE :=1234; Begin DELETE FROM emp WHERE empno=v_empno; End; 事务处理语句的使用 在PL/SQL中可以使用SQL的 COMMIT,ROLLBACK及 SAVEPOINT 语句. Declare v_empno EMP.empno%TYPE := 1234; Begin DELETE FROM emp WHERE empno = v_empno; COMMIT; End; / PL/SQL基础 在PL/SQL中执行DDL语句 begin execute immediate 'create table t ( num varchar2(20) default ''hello'')'; end 例外处理 (EXCEPTION) 许多编写得很好的程序都必须能够正确处理各种出错情况,并且尽 可能地从错误中进行恢复。异常处理方法是程序对运行时刻错误作 出反应并进行处理的方法。当引发一个异常情况时,控制便会转给 块的异常处理部分。 异常处理部分的语法如下: EXCEPTION WHEN 异常情况1 THEN 语句序列1; WHEN 异常情况2 THEN 语句序列2; ... WHEN OTHERS THEN 语句序列3; END; OTHERS 异常处理将对所有引发的异常情况执行 其代码。它应该是块中最后一个处理语句,确保 所有的错误都被检测到。但OTHERS 只是简单地 记录发生了错误,而没有记录发生的是哪一个错 误。我们可以在OTHERS 中用预定义函数 SQLCODE 和SQLERRM来决定引发异常处理的是哪个错误。 异常情况包括系统预定义的异常情况、用户自定 义的异常情况。 例外处理 (EXCEPTION) ⑴ 系统预定义的异常情况 ORACLE有一些预定义的异常情况和大多数通常的ORACLE错误是对 应的。这些异常情况所使用的标识符在包STANDARD90中进行定义。 在程序的异常处理部分直接对它们进行处理。 预定义的 ORACLE 异常情况 ORACLE 错误 对应的异常情况 说明 ORA-0001 DUP_VAL_ON_INDEX 唯一值约束被破坏 ORA-0051 TIMEOUT_ON_RESOURCE 在等待资源时发生超时现象 ORA-0061 TRANSACTION_BACKED_OUT 由于发生死锁事物处理被撤 消了 ORA-1001 INVALID_CURSOR 非法的游标操作 ORA-1012 NOT_LOGGED_ON 没有连接到 ORACLE ORA-1017 LOGIN_DENIED 无效的用户名/口令 ORA-1403 NO_DATA_FOUND 没有找到数据 ORA-1422 TOO_MANY_ROWS SELECT...INTO 语句匹配多 个行 ORA-1476 ZERO_DIVIDE 被零除 ORA-1722 INVALID_NUMBER 转换为一个数字失败。 ORA-6500 STORAGE_ERROR 如果 PL/SQL 运行时内存不 够就引发内部的 PL/SQL 错 误 ORA-6501 PROGRAM_ERROR 内部 PL/SQL 错误 ORA-6502 VALUE_ERROR 截尾、算术或转换错误 ORA-6504 ROWTYPE_MISMATCH 宿主游标变量和 PL/SQL 游 标变量有不兼容的行类型 ORA-6511 CURSOR_ALREADY_OPEN 试图打开已存在的游标 ORA-6530 ACCESS_INTO_NULL 试图为 NULL 对象的属性赋 值 ORA-6531 COLLECTION_IS_NULL 试图将 EXISTS 以外的集合 方法应用于一个 NULL PL/SQL 表或 VARRAY 上 ORA-6532 SUBSCRIPT_OUTSIDE_LIMIT 对嵌套表或 VARRAY 索引的 引用超出说明范围以外 ORA-6533 SUBSCRIPT_BEYOND_COUNT 对嵌套表或 VARRAY 索引的 引用大于集合中元素的个数 ⑵ 用户自定义的异常情况 用户自定义的异常情况是程序定义的一个错误。程序所定义的这个 错误并不一定非是一个ORACLE错误,它可能是与数据相关的一个错 误。 用户定义的异常情况的处理分三步: ⑵ 用户自定义的异常情况 · 定义异常情况 用户定义的异常情况是在PL/SQL块的说明部分进行定义的,和变量 相类似,异常情况有一个类型和作用域。 例如: my_exception exception; · 触发异常情况 当一个异常情况相关的错误出现时,就会引发该异常情况。用户定义 的异常情况是通过显式使用RAISE 语句来引发的,而预定义的异常情况 是当相关的ORACLE错误发生时被隐式触发的。 例如: RAISE MY_EXCEPTION; · 在程序的异常处理部分对定义的异常情况进行处理。 例如: WHEN MY_EXCEPTION THEN ... ⑵ 用户自定义的异常情况 例1: DECLARE tin_rec tin % rowtype ; v_passwd user.passwd % type ; err_ps EXCEPTION ; BEGIN select * into tin_rec from tin ; select passwd into v_passwd from user where userid = tin_rec.uid ; if tin_rec.ps = v_passwd then insert into tout values(‘ login ok’ ); else raise err_ps ; end if ; ⑵ 用户自定义的异常情况 exception when err_ps then insert into tout values(‘ password error’〕; when no_data_found then insert into tout values(‘ userid error’〕; end; ⑵ 用户自定义的异常情况 例2: declare e_toomanystudent exception; v_currentstudent number(3); v_maxstudent number(3); v_errorcode number; v_errortext varchar2(200); begin select current_student,max_students into v_currentstudent,v_maxstudent from classes where department=’HIS’ and course=101; if v_currentstudent > v_maxstudent then raise e_toomanyexception; end if; ⑵ 用户自定义的异常情况 exception when no_data_found or too_many_rows then dbms_output.put_line(‘发生系统预定义错误‘); when e_toomanyexception then insert into log_table(info) values(‘history 101 has ‘||v_currentstudent); when others then v_errorcoed:=sqlcode; v_errortext:=substr(sqlerrm,1,200); insert into log_table(code,message,info) values(v_errorcode,v_errortext,’oracle error occured’); end; / 例外处理 (EXCEPTION) 在PL/SQL中,警告信息、出错信息、或返回信息统称为例外(Exception) 有两中类型的例外。 Oracle预定义的例外: 是由PL/SQL运行过程中,系统自动产生的信息。 用户定义例外: 是用户根据需要,自己定义使用的例外,执行时 由用户自己引起。 预定义的例外 CURSOR_ALREADY_OPEN NO_DATA_FOUND TOO_MANY_ROWS INVALID_CURSOR VALUE_ERROR(算术、转换、截断或大小约束错误) INVALID_NUMBER(字符串转换为数字时失败) ZERO_DIVIDE DUP_VAL_ON_INDEX(给唯一约束列插入重复的值) 用户自定义例外 用户定义的例外必须在DECLARE段中说明,在 Begin段中用RAISE引起,在EXCEPTION段中使用。 例外处理 (EXCEPTION) declare v_temp number(4); begin select empno into v_temp from emp where deptno = 10; exception when too_many_rows then dbms_output.put_line('太多记录'); when others then dbms_output.put_line('error'); end; declare v_temp number(4); begin select empno into v_temp from emp where deptno = 1111; exception when no_data_found then dbms_output.put_line('没有数据'); when others then dbms_output.put_line('error'); end; 例外处理 (EXCEPTION) declare v_empinfo emp%rowtype; begin select * into v_empinfo from emp where empno=7369; insert into emp(empno,ename) values(v_empinfo.empno,v_empinfo.ename); exception when dup_val_on_index then dbms_output.put_line('插入重复值'); when others then null; end; 例外处理 (EXCEPTION) --将错误信息保存到表中 create table errorlog ( id number primary key, errorcode number, errormsg varchar(1024), errordate date ); --为了让id字段自动递增则: create sequence seq_errorlog_id start with 1 increment by 1; 例外处理 (EXCEPTION) 示例: declare v_deptnodept.deptno%type := 10; v_errcode number; v_errmsg varchar2(1024); begin delete from dept where deptno = v_deptno; commit; exception when others then rollback; v_errcode := SQLCODE; v_errmsg := SQLERRM; insert into errorlog values(seq_errorlog_id.nextval,v_errcode,v_errmsg,sysdate); commit; end; select to_char(errordate,'yyyy-mm-dd HH24:MI:SS') from errorlog; 游标 (CURSOR) 设计 游标的类型: 一是隐性游标 二是显性游标 使用select命令查询获得一行记录就是使用了隐性游标,它的特点是不 用使用游标的声明和只能操作一条记录;而显性的游标必须经过严格的 声明、打开和提取操作。 语法: CURSOR 光标名(参数) IS SELECT 字句; 打开游标 获取活动集中的行 FETCH语句检索活动集中的行,每次一行,每执行一次FECTCH,光 标前进到活动集中的下一行。 游标下移 关闭游标 游标属性 游标的属性 从游标工作区中逐一提取数据,可以在循环中完成。但循环的开 始以及结束,必须以游标的属性为依据。 游标属性及其描述如下: 游标属性 游标属性 描 述 游标名%ISOPEN 值为布尔型,如果游标已打开,取值为 TRUE 游标名%NOTFOUND 值为布尔型,如果最近一次 FETCH 操作没有返回结果,则取值 为 TRUE 游标名%FOUND 值为布尔型,如果最近一次 FETCH 操作没有返回结果,则取值 为 FALSE。否则,为 TRUE 游标名%ROWCOUNT 值为数字型,值是到当前为止返回的记录数 演示: • eg1: declare cursor c is select * from emp; v_emp c%rowtype; begin open c; fetch c into v_emp; dbms_output.put_line(v_emp.ename); close c; end; 演示: eg2: declare cursor c is select * from emp; v_emp c%rowtype; begin open c; loop fetch c into v_emp; exit when(c%notfound); dbms_output.put_line(v_emp.ename); end loop; close c; end; 演示: eg3: while循环 declare cursor c is select * from emp; v_emp c%rowtype; begin open c; fetch c into v_emp; while (c%found) loop fetch c into v_emp; dbms_output.put_line(v_emp.ename); end loop; close c; end; 演示: eg4: for循环 declare cursor c(v_deptno emp.deptno%type,v_job emp.job%type) is select ename,sal from emp where deptno = v_deptno and job = v_job; begin for v_temp in c(30,'CLERK') loop dbms_output.put_line(v_temp.ename); end loop; end; 例1:通过游标打印所有员工的信息: set serveroutput on declare cursor cur_emp is select empno,ename,job,hiredate,sal from emp; v_empno emp.empno%type; v_ename char(10); v_job char(10); v_hiredate emp.hiredate%type; v_sal emp.sal%type; begin open cur_emp; dbms_output.put_line(' 工号 姓名 工作 入职时间 工资 '); dbms_output.put_line(' -----------------------------------------------------------'); loop fetch cur_emp into v_empno,v_ename,v_job,v_hiredate,v_sal; dbms_output.put_line(' '||v_empno||' '||v_ename||' '||v_job||' '||v_hiredate||' '||v_sal); dbms_output.put_line(' ----------------------------------------------------------'); exit when cur_emp%notfound; end loop; close cur_emp; exception when invalid_cursor then dbms_output.put_line('无效游标。'); when cursor_already_open then dbms_output.put_line('游标已经打开。'); when others then dbms_output.put_line('其他异常'); end; 例2:打印所有部门信息,用while循环实现提取数据的操作。 declare cursor c_dept is select dname,loc from dept; v_dname dept.dname%type; v_loc dept.loc%type; begin open c_dept; fetch c_dept into v_dname,v_loc; while c_dept%found loop dbms_output.put_line(c_dept%rowcount); dbms_output.put_line(v_dname||' '||v_loc); fetch c_dept into v_dname,v_loc; end loop; close c_dept; exception when invalid_cursor then dbms_output.put_line('无效游标。'); when cursor_already_open then dbms_output.put_line('游标已经打开。'); when others then dbms_output.put_line('其他异常'); end; 例3:打印工资最高的前几行记录: create or replace procedure print_emp ( p_top number ) as cursor c_emp is select ename,job,sal from emp where sal in (select sal from (select distinct sal from emp order by sal desc) tab1 where rownum <= p_top) order by sal desc; v_ename emp.ename%type; v_job emp.job%type; v_sal emp.sal%type; begin open c_emp; loop fetch c_emp into v_ename,v_job,v_sal; exit when c_emp%notfound; dbms_output.put_line ( v_ename||' '||v_job||' '||v_sal); end loop; close c_emp; exception when others then null; end print_emp; ---------------- exec print_emp(4) 游标的属性: (1)%isopen判断游标是否被打开,如果打开,%isopen为true,否则为false。 (2)%found,%notfound判断游标是否指向有效记录,如果有效,则%found为true, 那么%notfound为false,否则%found为false,那么%notfound为true。 (3)%rowcount返回当前位置游标读取的记录数。 例如:打印工资高于3000的最低工资和此人的姓名,工作。 declare v_ename emp.ename%type; v_job emp.job%type; v_sal emp.sal%type; cursor cur_empsal is select ename,job,sal from emp where sal>3000 order by sal; begin if cur_empsal%isopen=false then open cur_empsal; dbms_output.put_line(v_ename||' end if; '||v_job||' '||v_sal); fetch cur_empsal into v_ename,v_job,v_sal; exception while cur_empsal%found loop when invalid_cursor then if cur_empsal%rowcount=2 then dbms_output.put_line('无效游标'); exit; when others then end if; null; fetch cur_empsal into v_ename,v_job,v_sal; end; end loop; ……………………… 游标for循环: 游标for循环是一种快捷的处理游标的方式,它使用for循环依次读取结果 集中的行数据,当for循环开始时,游标自动打开,每循环一次系统自动读取游标 当前行的数据,当退出for循环时,游标被自动关闭。 注意: 当使用游标for循环时不能使用open、fetch、close语句,否则会发生错误。 例如打印工资大于2500小于4000的员工的信息,包括姓名,工资,部门。 declare v_ename emp.ename%type; v_sal emp.sal%type; v_deptno emp.deptno%type; cursor cur_salary is select ename,sal,deptno from emp where sal>=2500 and sal<=4000; begin for cur_empsalary in cur_salary loop v_ename:=cur_empsalary.ename; v_sal:=cur_empsalary.sal; v_deptno:=cur_empsalary.deptno; dbms_output.put_line(v_ename||' '||v_sal||' '||v_deptno); end loop; exception when invalid_cursor then null; when others then null; end; 5、带参数的游标:与存储过程相似,可以将参数传给游标并在查询中使用 ,对处理在某些条件下打开的游标的情况非常有用。 语法如下: cursor cursor_name(parameter datatype [default value][,…]) is select statement; 注意: 与存储过程不同,游标只能有In参数,参数定义类型不能指定长度。参数 也可以有缺省值,游标定义的参数只能在游标内使用,不要在超出游标以外 的程序中使用。给游标参数传递值在游标打开时进行。 语法如下:open cursor_name(value[,…]); 例1:查找所有部门的部门号,部门名称,部门的工资工资总额。 declare cursor curdept is select * from dept order by deptno; cursor cur_e(p_deptno dept.deptno%type) is select nvl(sum(e.sal),0) from dept d , emp e where d.deptno=e.deptno and d.deptno=p_deptno; r_dept dept%rowtype; v_deptno dept.deptno%type; v_dname dept.dname%type; v_sumsal number; begin open curdept; loop fetch curdept into r_dept; exit when curdept%notfound; dbms_output.put_line('部门号:'||r_dept.deptno||' '||r_dept.dname); v_sumsal := 0; open cur_e(r_dept.deptno); fetch cur_e into v_sumsal; dbms_output.put_line('总工资:'||v_sumsal); close cur_e; end loop; close curdept; end; 游标中的更新和删除 • UPDATE或DELETE语句中的WHERE CURRENT OF子串专门处理 要执行UPDATE或DELETE操作的表中取出的最近的数据。要使用这 个方法,在声明游标时必须使用FOR UPDATE子串,当对话使用 FOR UPDATE子串打开一个游标时,所有返回集中的数据行都将处 于行级(ROW-LEVEL)独占式锁定,其他对象只能查询这些数据行, 不能进行UPDATE、DELETE或SELECT...FOR UPDATE操作。 • 语法: FOR UPDATE [OF [schema.]table.column[,[schema.]table.column].. • 在多表查询中,使用OF子句来锁定特定的表,如果忽略了OF子句,那 么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会 话锁定,那么正常情况下ORACLE将等待,直到数据行解锁。 在UPDATE和DELETE中使用WHERE CURRENT OF子串的语法如 下: WHERE{CURRENT OF cursor_name|search_condition} 游标中的更新和删除 • DELCARE CURSOR c1 IS SELECT empno,salary FROM emp WHERE comm IS NULL FOR UPDATE OF comm; v_comm NUMBER(10,2); BEGIN FOR r1 IN c1 LOOP IF r1.salary<500 THEN v_comm:=r1.salary*0.25; ELSEIF r1.salary<1000 THEN v_comm:=r1.salary*0.20; ELSEIF r1.salary<3000 THEN v_comm:=r1.salary*0.15; ELSE v_comm:=r1.salary*0.12; END IF; UPDATE emp; SET comm=v_comm WHERE CURRENT OF c1l; END LOOP; END 过程 • 过程(PROCEDURE):可以没有返回值。 • 基本格式如下: CREATE [ OR REPLACE ] PROCEDURE 过程名 ( 参数名1 IN/OUT 参数类型, 参数名2 IN/OUT 参数类型 ) IS {变量声明部分} BEGIN {过程部分} EXCEPTION {异常处理部分} END; 用法演示: --存储过程 a.不带参数 执行存储过程(2种方法): 1. exec p; 2. begin p; create or replace procedure p end; is cursor c is select * from emp for update; begin for v_temp in c loop if(v_temp.deptno = 10) then update emp set sal = sal + 10 where current of c; elsif(v_temp.deptno = 20) then update emp set sal = sal + 20 where current of c; else update emp set sal = sal + 50 where current of c; end if; end loop; commit; end; 用法演示: --存储过程 b.带参数 create or replace procedure p (v_a in number,v_b number,v_ret out number,v_temp in out number) is begin if(v_a > v_b) then v_ret := v_a; else v_ret := v_b; end if; v_temp := v_temp + 1; end; 调用: declare v_a number := 3; v_b number := 4; v_ret number; v_temp number := 5; begin p(v_a,v_b,v_ret,v_temp); dbms_output.put_line(v_ret); dbms_output.put_line(v_temp); end; --show error; --drop procedure p; 存储过程的查看 select * from user_procedures where object_name = ‘过 程名'; 如果有错误,则显示错误信息 删除存储过程 用法演示: --存储过程 b.带参数 • create or replace procedure query_emp ( v_no in emp.empno%type, v_name out emp.ename%type, v_sal out emp.sal%type ) is e_sal_error exception; begin select ename,sal into v_name,v_sal from emp where empno=v_no; if v_sal >=2500 then dbms_output.put_line('该雇员工资:'||v_no); raise e_sal_error; end if; exception when no_data_found then dbms_output.put_line('没有该雇员:'||v_no); when e_sal_error then dbms_output.put_line('该雇员工资高于2500了'); end query_emp; 用法演示: --存储过程 b.带参数 存储过程的调用 variable a1 varchar2(16); variable a2 number; execute query_emp(7788,:a1,:a2); 或 declare v_a1 emp.ename%type; v_a2 emp.sal%type; begin query_emp(v_name => v_a1,v_sal => v_a2,v_no =>5678); end; 示例: 函数 • PL/SQL: • 函数 –函数用于计算和返回特定的数据.可以将经常需要进行的计 算写成函数.函数的调用是表达式的一部分,而过程的调用是 一条PL/SQL语句. –语法: create [or replace] function fun_name ([para_name [in | out | in out) type [, ….]]) return type is | as 声明部分 begin statement; end fun_name; 函数 例如:利用函数实现一个查询获取某雇员的工资 CREATE OR REPLACE FUNCTION get_sal (v_emp_no IN emp.empno%TYPE) RETURN NUMBER IS v_emp_sal emp.sal%TYPE:=0; BEGIN SELECT sal INTO v_emp_sal FROM emp WHERE empno=v_emp_no; RETURN(v_emp_sal); END get_sal; 函数 • 用法演示: --创建函数: create or replace function sal_tax ( v_sal number ) return number is begin if(v_sal < 2000) then return 0.10; elsif(v_sal < 2750) then return 0.15; else return 0.20; end if; end; --使用函数 select sal_tax(sal) from emp; 函数 • 用法演示: --创建函数: create or replace function get_salary_by_deptno ( v_dept_no in emp.deptno%type, v_emp_cnt out number ) return number is v_sum number(10,2); begin select sum(sal),count(*) into v_sum,v_emp_cnt from emp where deptno = v_dept_no; return v_sum; end get_salary_by_deptno; 函数 • 用法演示: 调用函数1 variable a1 number; variable a2 number; execute :a1 := get_salary_by_deptno (10,:a2); print :a1 :a2 • PL/SQL: • 函数的查看 – select text from user_source where name = ‘函数名‘ • 删除 – drop function 函数名; 函数 • 用法演示: 调用函数2 set serveroutput on; declare v_a1 emp.deptno%type; v_a2 number; v_sum number(10,2); begin v_sum := get_salary_by_deptno(v_emp_cnt => v_a1,v_dept_no => 10); if v_a1 = 0 then dbms_output.put_line('该部门无人'); else dbms_output.put_line('该部门工资总和:'||v_sum||'人数 '||v_a2); end if; end; 触发器 • 触发器是一种特殊的存储过程,也就是说触发器具有存储过程的所有优势,是命 名程序的一种,也是存储并运行在服务器端的。说其具有特殊性,是因为触发器 的调用执行和存储过程不一样,存储过程的程序调用执行必须由程序员事先设计 并编写好调用程序代码及对应的参数值,即存储过程的调用执行由程序员决定, 而触发器程序不能被应用程序调用,也没有参数,当触发器程序创建并保存在数 据库中后只要对应触发器触发器事件的发生,该触发器就会被自动调用执行。 1、触发器程序的分类, (1)按触发事件分为: 1)DML触发器:由insert、 update、 delete等操作触发的触发器称为DML触发 器。DML触发器是传统的触发器,可以使用DML触发器实现复杂的数据完整性。 2)DDL触发器:由create、alter、drop操作触发的触发器称为DDL触发器。使 用DDL触发器实现跟踪用户对数据库的DDL操作。 3)系统触发器:用户连接或断开数据库,由logon、 logoff、 shutdown、 startup操作触发的触发器称为系统触发器,使用系统触发器实现对用户连接数据 库信息和数据库启动或停止的信息。 (2)按照执行特点分为: 1)替代触发器:创建于视图上,触发事件和DML触发器相同,实现通过结构复 杂的视图修改基表的数据,实现维护数据的完整性。 2)一般触发器:DML、DDL、系统触发器又称为一般触发器。 触发器 2、创建DML触发器: (1)语法: create or replace trigger trigger_name before|after DML_statement [of colum,…] on table_name|view [when(condition)] [for each row] [declare declarations] begin execute statement exception end trigger_name; (2)语法说明: 1)trigger:触发器的关键字。 2)before|after:确定触发器程序的执行时机。 3)DML_statement:触发事件如update,delete,insert。 4)of column:基于列及的触发器。 5)on table_name|view:确定触发器的载体。 6)when(condition):触发条件。 7)for each row:基于行级的触发器。 8)declare:变量常量的声明位置,Oracle触发器没有is或as。触发器没有参数。 触发器 (3)实例: 1)创建简单触发器,并测试。 --创建日志信息表 create or replace trigger tri_insertemp1 set serveroutput on create after tableinsert emp_his as select * from emp where empno=3000; on emp1 insert into emp1(empno) delete begin from emp_his; values(3333); dbms_output.put_line('触发器程序执行了'); --创建触发器 end tri_insertemp1; create or replace trigger del_emp 2)创建相应update触发器。(独立实现) before delete on emp1 (4)触发器的级别:Oracle触发器分为行级触发器和语句级触发器。行级触发器表示DML操作每影响一 行记录时触发器程序就被执行一次。而语句级触发器每执行一次DML语句就会调用执行触发器一次, for each row 与影响的行无关。使用for each row参数设置触发为行级触发器如果不指定都是语句级触发器。 begin 1)关于:new和:old变量的解释: :new和:old是在触发器被触发时产生的两个特殊的变量,它们数据类 型triggering_table%rowtype类型,该变量的值是DML触发器所影响的记录,在编写触发器时可以将 -- 将修改前数据插入到日志记录表 emp_his,以供监督使用。 它们当作rowtype类型来处理它们。:new变量中保存的是insert或update时的新数据。:old变量中保 insert into emp_his(empno, ename , job ,mgr , hiredate, sal , comm, deptno) 存的是执行delete触发器将要被删除的数据和执行update触发器时要被更新的数据即表中的原数据。 values(:old.empno, :old.ename , :old.job,:old.mgr, :old.hiredate, :old.sal, :old.comm,:old.deptno); INSERT UPDATE DELETE end; 特性 NEW --测试触发器 有效 OLD where empno=3000; NULL delete emp 有效 NULL 有效 有效 2)实例1: 建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中。 (独立实现) 触发器 3)实例1:实现emp1表的sal字段的default属性,默认值为1000。 create or replace trigger tri_default before insert on emp1 for each row begin if :new.sal is null then :new.sal:=1000; end if; exception when dup_val_on_index then null; when others then null; end; insert into emp1(ename) values(‘aaa’); 实例2:实现某字段的自动编号功能。 create sequence sque; create or replace trigger tri_autoempno before insert on emp1 for each row declare v_empno emp1.empno%type; begin select sque.nextval into v_empno from dual; if :new.empno is null then :new.empno:=v_empno; end if; end; insert into emp1(ename) values(‘bbb’); 触发器 (5)字段级触发器:DML触发器用于实现复杂的数据完整性,而且通常都是行级触发器,这 时必须精确触发器程序触发的时机,否则触发器会被无意义的触发执行,这样势必会浪费 服务器资源,字段级触发器能够精确触发器触发的时机为必须操作某个或某几个字段时才 会执行触发器程序。 实例1:实现check约束,约束员工的工资在0~10000之间。 set serveroutput on create or replace trigger tri_checkemp1 before insert on emp for each row begin if :new.sal>10000 or :new.sal<0 then :new.empno:=null; :new.ename:=null; :new.job:=null; insert into emp :new.mgr:=null; values(1111,'aaa','ccc',1111,sysdate,1500,0,10); :new.hiredate:=null; :new.sal:=null; :new.comm:=null; :new.deptno:=null; end if; exception when others then null; end; 触发器 (6)when子句在触发器中的应用:when()子句在触发器中限制行级触发器被触 发的条件,如果when()子句的条件表达式返回true,则触发器的程序体被执行 ,否则不执行触发器程序体,当使用了when子句,它包含的条件表达式会为行 级触发器的每行数据进行一次判断,when子句更加精确的确定触发器触发的时 机,在when中如要用到:new和:old时,不带冒号。 例:当修改员工工资时,如果修改后的工资高于5000,则打印原工资和工资差信息 create or replace trigger print_update_sal_info after update of sal,comm on emp for each row when (new.sal>5000) declare v_diff number; begin v_diff := :new.sal - :old.sal; dbms_output.put_line('原工资-->新工资:工资差'); dbms_output.put_line(:old.sal||'-->'||:new.sal||':'||v_diff); exception when others then null; end print_update_sal_info; update emp set sal= 6000 where empno = 1; 触发器 (7)DML触发器的触发谓词:一个触发器可以定义为响应多个触发事件的触发器 ,如before insert or update or delete on table,当向表中添加数据、修改数 据、删除数据时触发触发器。判定是由哪种触发事件触发的触发器,就要使用 谓词。触发器的谓词包括: 1)inserting:当执行insert操作时,inserting返回true。 2)updating:当执行update操作时,updating返回true。 3)deleting:当执行delete操作时,deleting返回true。 例: 实现跟踪emp表的dml操作日志信息,包括记录操作的用户名、操作时间、操 作类型等信息。 A、创建日志信息表: create table emp_dml_log ( logno number(20), operuser varchar2(30), opertime date, opertype varchar2(10) ); 触发器 B、创建触发器: create or replace trigger emp_dml_log after insert or update or delete on emp for each row begin if inserting then insert into emp_dml_log values(seq2.nextval,user,sysdate,'INSRET'); elsif updating then insert into emp_dml_log values(seq2.nextval,user,sysdate,'UPDATE'); elsif deleting then insert into emp_dml_log values(seq2.nextval,user,sysdate,'DELETE'); end if; end emp_dml_log; 触发器 (8)instead of 触发器:我们已经学习了before触发器和after触发器, before触发器称为前触发器,即触发器程序优先于触发事件执行,after 触发器是触发事件优先于触发器程序的执行而执行。instead of触发器是 这样一种触发器:即触发事件不真正执行,只执行触发器程序。 通过视图修改多基表数据,没有修改成功,这是因为通过视图不允许同时 修改多基表数据。但通过建立基于多基表的视图就可以完成这样的功能。 1)创建instead of触发器语法: CREATE [OR REPLACE] TRIGGER trigger_name INSTEAD OF {INSERT | DELETE | UPDATE [OF column [, column …]]} ON [schema.] view_name [REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}] [FOR EACH ROW ] trigger_body; 2)实例1:通过多基表视图修改表中数据: 触发器 A、创建多基表视图: create or replace view titles_sales as select t.title_id,t.title,t.price,s.ord_date,s.qty from titles t,sales s where t.title_id=s.title_id; B、通过视图修改qty数据: update titles_sales ERROR 位于第 1 行: set price=price+1,qty=qty-1 ORA-01732: 此视图的数据操纵操作非法 where title_id='BU1111'; C、创建替代触发器: create or replace trigger tri_t_s instead of update on titles_sales begin D、通过instead of 触发器实现price、qty数据的修改: update titles update titles_sales set price=price+1 set price=price+1,qty=qty-1 where title_id=:old.title_id; where title_id='BU1111'; update sales set qty=qty+1 where title_id=:old.title_id; end; 触发器 实例2:通过empinfo视图,智能向emp10,emp20表中添加10号和20号部门员工信息。 A、创建表: create table emp10 as select * from emp where deptno = 10; create table emp20 as select * from emp where deptno = 20; B、创建多基表视图: create view empinfo as select * from emp10 union select * from emp20; C、测试 insert into empinfo(empno,ename,deptno) values(2,'aaa',10); -- 不可能执行成功。 触发器 D、创建触发器: create or replace trigger auto_add_emp instead of insert on empinfo declare begin if :new.deptno = 10 then insert into emp10 values(:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno); elsif :new.deptno = 20 then insert into emp20 values(:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno); else dbms_output.put_line('只能添加10,20号部门员工信息。'); end if; end tri_name; E、重新测试: insert into empinfo(empno,ename,deptno) values(2,'aaa',10); --执行成功。 注意:当删除表或者视图时,触发器也随之被删除。 触发器 3、触发器的限制 (1)CREATE TRIGGER语句文本的字符长度不能超过32KB; (2)触发器体内的SELECT 语句只能为SELECT…INTO…结构,或为定义游标所使用的SELECT 语句。 (3)触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句; (4)由触发器所调用的过程或函数也不能使用数据库事务控制语句; (5)触发器中不能使用LONG, LONG RAW 类型; (6)触发器内可以参照LOB 类型列的列值,但不能通过 :NEW 修改LOB列中的数据; 4、创建DDL触发器: (1)语法: create or replace trigger trigger_name before|after create[ or alter or drop] on schema | database [ declare declarations ] begin excute statements; [ exception exception handles; ] end [tirgger_name]; 语法说明: on schema: 模式(用户)级触发器,只有触发器的创建者执行ddl操作才能触发。 on database: 数据库级触发器,数据库中任何用户执行ddl操作都触发。 注意:用户必须具有 administer database trigger的系统权限,才能创建数据库级触发器。只有管理员才能 给其他用户如scott赋予此权限。 触发器 (2)实例:跟踪数据库用户的DDL操作日志,包括用户名、操作时间、数据库对象名、对象 所有者、对象类型、操作信息。 1)创建日志表: create table ddl_log ( logno number(20) primary key, uname varchar2(30), oper_time date, obj_name varchar2(30), obj_owner varchar2(30), obj_type varchar2(20), oper_type varchar2(20) ); --测试 create table emp4 as select * from emp; --查询: select * from ddl_log; 2)创建序列,确定主键值。 create sequence seq_ddl_log; 3)在日志表上创建触发器: create or replace trigger ddl_log after create or alter or drop on database begin insert into ddl_log values(seq_ddl_log.nextval,user,sysdate,sys.dictionary_obj_name,sys.dictionary_obj_owner, sys.dictionary_obj_type,sys.sysevent); end ddl_log; 触发器 5、创建系统事件触发器 系统触发器就是相应数据库系统事件的触发器,包括数据库服务器的启动或关闭,用户的 登录与退出、数据库服务错误等。创建系统触发器的语法如下: CREATE OR REPLACE TRIGGER [sachema.] trigger_name {BEFORE|AFTER} {database_event_list} ON { DATABASE | [schema.] SCHEMA } [WHEN_clause] trigger_body; database_event_list:一个或多个数据库事件,事件间用 OR 分开; 触发器 实例1:创建LOGON、STARTUP和SERVERERROR 事件触发器 create or replace trigger trig_after after logon or startup or servererror on database declare v_event varchar2(20); v_instance number; v_err_num number; v_dbname varchar2(50); v_user varchar2(30); begin v_event := sysevent; if v_event= 'logon' then v_user := login_user; insert into eventlog(eventname, username) values(v_event, v_user); elsif v_event = 'servererror' then v_err_num := server_error(1); Insert into eventlog(eventname, srv_error) values(v_event, v_err_num); else v_Instance := instance_num; v_dbname := database_name; Insert into eventlog(eventname, inst_num, db_name) values(v_event, v_instance, v_dbname); end if; end; --系统操作的日志信息表 create table eventlog ( eventname varchar2(20), username varchar2(30), db_name varchar2(50), inst_num number, srv_error number ); 触发器 实例2:创建LOGOFF和SHUTDOWN 事件触发器 create or replace trigger trig_before before logoff or shutdown on database declare v_event varchar2(20); v_instance number; v_dbname varchar2(50); v_user varchar2(30); begin v_event := sysevent; if v_event = 'logoff' then v_user := login_user; insert into eventlog(eventname, username) values(v_event, v_user); else v_instance := instance_num; v_dbname := database_name; insert into eventlog(eventname, inst_num, db_name) values(v_event, v_instance, v_dbname); end if; end; 6、删除触发器: drop trigger trigger_name; SQL优化初步 • SQL变量或字段的类型匹配: 对于数值型变量或字段不需要加单引号,字符型需要加单 引号。 • 例: SELECT * FROM tcm_user WHERE user_id = ‘35627836’; SELECT * FROM tbm_newfun_reso WHERE newfunc_id = 9016; 避免复杂的多表关联 Select … From user_files uf, df_money_files dm, cw_charge_record cc Where uf.user_no = dm.user_no and dm.user_no = cc.user_no and …… and not exists(select …) ??? 很难优化,随着数据量的增加性能的风险很大。 使用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%’; 减少对表的查询 • 在含有子查询的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) 用NOT EXISTS替代NOT IN • 在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查 询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我 们可以把它改写成外连接(Outer Joins)或NOT EXISTS. • 例如: 低效 SELECT USER_NO FROM CW_ARREARAGE WHERE USER_NO NOT IN (SELECT USER_NO FROM USER_FILES); • 高效 SELECT USER_NO FROM CW_ARREARAGE CA WHERE NOT EXISTS (SELECT 1 FROM USER_FILES UF WHERE UF.USER_NO = CA.USER_NO); 用>=替代> • 如果DEPTNO上有一个索引 • 高效: SELECT * FROM EMP WHERE DEPTNO >=4 • 低效: SELECT * FROM EMP WHERE DEPTNO >3 SQL优化 • 索引的使用: 1、主键索引(非空且不存在重复值)的效率最高,对于主键条 件查询的语句不必加分区字段作为条件(如city_id); 2、对于数据量较小的表,索引不会有明显的作用; 3、索引只会加快查询速度,对于大批量的插入和更新操作 会降低效率; 4、对于组合索引只对第一个字段做查询时起作用; 5、对于值重复率太高的字段,索引不会发挥最高作用; SQL优化 • 对于并列条件的解析顺序:从后向前。表关联语句放在最 前,将能过滤掉最大记录数的条件放在最后。 • 例:对于分区表的非主键查询,应将分区字段的查询条件 (如city_id)放在最后。 • 表连接时的扫描顺序:从后向前,将效率最高的表放在最 后。 SQL优化 • 过程中尽量不要用到*号,*号会首先查询数据字典翻译字 段名,应用字段名代替。 • 对于模糊查询LIKE,只有在用到字段的前几位字符的时 候才会用到该字段的索引,以通配符开始的模糊查询不会 用到索引。 • 例:假设tcm_user表的user_name建有索引,则 SELECT * FROM tcm_user WHERE user_name like ‘王%’; (会用到索引) SELECT * FROM tcm_user WHERE user_name like ‘%熹微%’; (不会用到索引) SQL优化 • 对于表关联操作时的非歧义字段,加上表名或表别名会省 下检索数据字典的时间,提高效率。 • 例:效率高的写法: SELECT a.*, b.pay_type_name FROM tcm_user a, tvlbm_pay_type b WHERE a.pay_rule_id = b.pay_type_id; 效率低的写法: SELECT a.*, pay_type_name FROM tcm_user a, tvlbm_pay_type b WHERE pay_rule_id = pay_type_id; SQL优化 • EXISTS与IN • 例: • 效率低的写法: SELECT * FROM tpm_serv WHERE serv_id IN (SELECT newfunc_id FROM tbm_newfun_reso WHERE city_id = ’03’); 效率高的写法: SELECT * FROM tpm_serv a WHERE EXISTS (SELECT * FROM tbm_newfun_reso WHERE newfunc_id = a.serv_id AND city_id=‘03’); 效率最高的写法: SELECT * FROM tpm_serv a WHERE EXISTS (SELECT 1 FROM tbm_newfun_reso WHERE newfunc_id = a.serv_id AND city_id = ‘03’); 小结 • PL/SQL语言基础 • PL/SQL语言的简介 • 数据类型 • 程序结构 • 流程控制 • 异常处理 • 过程与函数 • 游标 • 触发器 • SQL优化初步