PLSQL

select deptno,

ename,

sal,

sum(sal) over (partition by deptno order by sal, ename) cum_sal,

round(100*ratio_to_report(sal) over (partition by deptno), 1) pct_dept,

round(100*ratio_to_report(sal) over (), 1) pct_over_all

from emp order by deptno, sal;

 

 

 

 

 

DECLARE

v_sal EMP.SAL%TYPE;

BEGIN

SELECT SAL INTO V_SAL FROM EMP WHERE EMP.EMPNO=7369;

CASE

        WHEN V_SAL BETWEEN 500 and 900 THEN DBMS_OUTPUT.PUT_LINE('大于500,小于900');

        WHEN V_SAL BETWEEN 900 and 1500 THEN DBMS_OUTPUT.PUT_LINE('大于900,小于1500');

     ELSE DBMS_OUTPUT.PUT_LINE('其他');

END CASE;

END;

 

 

SELECT DECODE(SAL,800,800*1.1,900,900*1.1,1000*1.1)

FROM EMP

WHERE EMP.EMPNO=7369;

 

 

merge into products p using newproducts np on (p.product_id = np.product_id)

when matched then

update set p.product_name = np.product_name

when not matched then

insert values(np.product_id, np.product_name, np.category)

 

注意ROUND是四舍五入,TRUNC是截取

 

 

 

数字有三种基本类型:

NUMBER可以描述整数或实数

PLS_INTEGER和BINARY_INTEGER只能描述整数

PLS_INTEGER和BINARY_INTEGER唯一区别:

在计算当中发生溢出时,BINARY_INTEGER型的变量会被自动指派给一个NUMBER型而不会出错,PLS_INTEGER型的变量将会发生错误

 

 

集合类型

单列多行数据,使用集合

pl/sql集合类型包括关联数组Associative array(索引表 pl/sql table)、嵌套表(Nested Table)、变长数组(VARRAY)。

Nested table与VARRY既可以被用于PL/SQL,也可以被直接用于数据库中,但是Associative array不行

Associative array是不能通过CREATE TYPE语句进行单独创建,只能在PL/SQL块(或Package)中进行定义并使用(即适用范围是PL/SQL Block级别)

Nested table与VARRAY则可以使用CREATE TYPE进行创建(即适用范围是Schema级别),它们还可以直接作为数据库表中列的类型

Associative array

DECLARE

TYPE va_planguage IS TABLE OF VARCHAR2(10) INDEX BY PLS_INTEGER;

lang va_planguage;

idx PLS_INTEGER;

BEGIN

lang(1):='java';

lang(9):='C#';

lang(3):='C++';

idx:=lang.FIRST;

WHILE(idx IS NOT NULL) LOOP

    DBMS_OUTPUT.PUT_LINE(lang(idx));

    idx:=lang.NEXT(idx);

END LOOP;

END;

 

与Associative array不同,Nested table变量需要显式初始化。Nested table初始化之后还需要调用EXTEND过程,扩展集合的"容量"

DECLARE

TYPE nt_planguage IS TABLE OF VARCHAR2(10);

lang nt_planguage;

BEGIN

lang:=nt_planguage('java','C#','C++');

    FOR i IN 1..lang.COUNT LOOP

        DBMS_OUTPUT.PUT_LINE(lang(i));

    END LOOP;

END;

 

 

 

VARRAY可以在声明时限制集合的长度。其索引总是连续的,而Nested table的索引在初始化赋值时是连续的,不过随着集合元素被删除,可能变得不连续

如果你需要允许使用负数索引,应该选择Associative array;

如果你需要限制集合元素的个数,应该选择VARRAY

 

 

 

 

 

 

用户自定义类型

常用内置函数

函数 说明 转换前的类型

TO_CHAR 转换成VARCHAR类型 数字型、日期型

TO_DATE 转换成DATE 字符型

TO_NUMBER 转换成number类型 字符型

 

IF语句

    IF V_SAL<500 THEN

        UPDATE EMP SET SAL=SAL*1.5 WHERE EMPNO=7788;

    ELSIF V_SAL<1500 THEN

        UPDATE EMP SET SAL=SAL*1.3 WHERE EMPNO=7788;

    ELSE

        UPDATE EMP SET SAL=SAL*1.0 WHERE EMPNO=7788;

    END IF;

 

 

 

 

 

循环语句

LOOP循环

DECLARE

    V_COUNTER NUMBER:=1;

BEGIN

    LOOP

        INSERT INTO TEMP_TABLE VALUES(V_COUNTER,'loop index');

        V_COUNTER:=V_COUNTER+1;

        IF V_COUNTER>50 THEN

        EXIT;

        END IF;

    END LOOP;

END;

 

WHILE循环

DECLARE

    V_COUNTER NUMBER:=1;

BEGIN

    WHILE V_COUNTER <=50 LOOP

        INSERT INTO TEMP_TABLE VALUES(V_COUNTER,'loop index');

        V_COUNTER:=V_COUNTER+1;

    END LOOP;

END;

 

 

 

 

 

FOR语句

DECLARE

    V_COUNTER NUMBER;

BEGIN

    FOR V_COUNTER IN 1..50 LOOP

        INSERT INTO TEMP_TABLE VALUES(V_COUNTER,'loop index');

    END LOOP;

END;

FOR v_counter IN REVERSE 1..50 LOOP

其中:IN:表示索引变量的值小到大 IN REVERSE:表示索引变量的值从大到小

 

 

GOTO语句

DECLARE

    V_VONTER NUMBER:=1;

BEGIN

    LOOP

        INSERT INTO TEMP_TABLE VALUES(V_VONTER,'loop count');

        V_VONTER:=V_VONTER+1;

        IF V_VONTER > 50 THEN

                GOTO ENDOFLOOP;

        END IF;

    END LOOP;

    <<ENDOFLOOP>>

        INSERT INTO TEMP_TABLE VALUES(-1,'done!');

END;

对于块、循环或IF语句而言,想要从外层跳到内层是非法的

从一个IF子句调转到IF的另一个子句中是非法的

出错处理块不能实行跳转

 

异常处理

系统异常

异常代码 异常名称 说明

ORA-01403 NO_DATA_FOUND 查询没有返回数据

ORA-01422 TOO_MANY_ROWS SELECT….INTO 语句返回多行结果

DECLARE

    V_COMM EMP.COMM%TYPE;

BEGIN

    SELECT COMM INTO V_COMM FROM EMP WHERE EMPNO=7839;

EXCEPTION

    WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('no data!');

    WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE('too many row');

    WHEN OTHERS THEN null;

END;

 

 

用户自定义异常

三个步骤:定义错误,触发错误,处理错误

DECLARE

    e_toomanystudent EXCEPTION;/*定义错误*/

    v_currentstudent NUMBER(3);

    v_maxstudent NUMBER;

    v_errortext VARCHAR2(200);

BEGIN

    SELECT current_student,max_student INTO v_currentstudent,v_maxstudent

    FROM classes WHERE department='HIS' AND course=101;

    IF v_currentstudent>v_maxstudent THEN

        RAISE e_toomanystudent; /*触发错误*/

    END IF;

EXCEPTION

    WHEN no_data_found OR too_many_rows THEN

        DBMS_OUTPUT.PUT_LINE('发生系统预定义错误');

    WHEN e_toomanystudent THEN /*处理错误*/

        INSERT INTO log_table(info) VALUES('history 101 has'|| v_currentstudent);

    WHEN others THEN

        v_errortext=sqlcode;

        v_errortext=substr(sqlerrm,1,200);

        INSERT INTO log_table(code,message,info)

VALUES(v_errortext,v_errortext,'Oracle error occured');

END;

 

 

 

游标

游标有两种类型:显示游标和隐式游标

显示游标:是由程序员定义和命名的,并且在块的执行部分中通过特定语句操作的内存工作区。

隐式游标:是由PL/SQL为DML语句和SELECT语句隐式定义的工作区

游标是一个不含INTO子句的SELECT语句

SELECT语句允许带WHERE,ORDER BY,GROUP BY等子句。

游标的定义

显示游标的处理步骤

显示游标的一些属性

%ISOPEN

%NOTFOUND

%FOUND

%ROWCOUNT

%ROWCOUNT Number 值是当前为止返回的记录数,初值为0,每取一条记录,该属性值加1。

DECLARE

v_deptno emp.deptno%type;

v_ename emp.ename%type;

v_sal emp.sal%type;

CURSOR emp_cursor IS SELECT ename ,sal FROM emp;/*定义游标*/

BEGIN

OPEN emp_cursor;/*打开游标*/

LOOP

FETCH emp_cursor INTO v_ename , v_sal;/*将当前行结果提取到变量中*/

        DBMS_OUTPUT.PUT_LINE(v_ename||' '||v_sal);

EXIT WHEN emp_cursor%NOTFOUND;/*判断数据行是否读取完*/

INSERT INTO temp VALUES(v_ename,v_sal);

END LOOP;

CLOSE emp_cursor;/*关闭游标*/

END;

/

Pl/sql语言中的&

V_deptno emp.deptno%TYPE :=&p_deptno;

Oracle中将带&的定义为变量,用户输入值后才能查询.
select * from table where id = &insertid
&insertid就是变量
打开Oracle的PL/SQL网页,
输入select * from table where id = &insertid
会出现一个文本框,要输入的值就是语句里面带&号的变量

 

 

游标的FOR循环

使用游标的FOR循环,可以简化游标的操作步骤

游标的FOR循环隐式(自动)地完成三个步骤:打开游标;(FETCT)取数据;关闭游标

记录名师系统隐式定义的游标名%ROWTYPE类型的记录变量,不必事先定义

DECLARE

v_deptno EMP.deptno%TYPE:=&p_deptno;

CURSOR emp_cursor IS SELECT ename,sal FROM EMP

WHERE deptno=v_deptno;

BEGIN

    FOR emp_record IN emp_cursor LOOP

        INSERT INTO temp(ename,sal) VALUES(emp_record.ename,emp_record.sal);

    END LOOP;

    COMMIT;

END;

/

 

要操纵数据库中数据,在定义游标的查询语句时,必须加上FOR UPDATE OF从句,表示要对表加锁。

表加锁后,在UPDATE或DELETE语句中,加WHERE CURRENT OF子句,既可以对锁定的数据进行修改

要使用这个方法,在声明游标时必须使用FOR UPDATE子串,当对话使用FOR UPDATE子串打开一个Oracle游标时,所有返回集中的数据行都将处于行级(ROW-LEVEL)独占式锁定,其他对象只能查询这些数据行,不能进 行UPDATE、DELETE或SELECT...FOR UPDATE操作。

使用of子句在特定表加行共享锁。

在多表查询中,使用OF子句来锁定特定的表,如果忽略了OF子句,那么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会话锁定,那么正常情况下ORACLE将等待,直到数据行解锁。
使用nowait子句
使用for update语句对被作用于行加锁,如果其他会话已经在被作用于行上加锁,那么默认情况下当前会话要一直等待对方释放锁,通过在for update子句中指定 nowait语句,可以避免等待锁,当指定了nowait子句之后,如果其他会话已经在被作用行加锁,那么当前会话会显示错误提示信息,并退出。

CURSOR 游标名IS

SELECT 列1,列2 … FORM 表 WHERE 条件

FOR UPDATE [OF column][NOWAIT];

带WHERE CURRENT OF 从句的UPDATE语句和DELETE语句

DELETE FROM 表 WHERE CURRENT OF 游标名;

UPDATE 表 SET 列1=值1,列2=值2... WHERE CURRENT OF 游标名;

 

 

 

Eg:查询emp表某部门的雇员情况,如果雇员的工资小于800,则将其工资改为800

DECLARE

DECLARE

v_deptno EMP.deptno%TYPE:=30;

v_empno EMP.empno%TYPE;

v_job EMP.job%TYPE;

v_sal EMP.SAL%TYPE;

CURSOR emp_cursor IS SELECT ename,job,sal FROM EMP

WHERE deptno=v_deptno FOR UPDATE OF sal;

BEGIN

    FOR emp_record IN emp_cursor LOOP

        IF emp_record.sal<800 THEN

            UPDATE EMP SET sal=800 WHERE CURRENT OF emp_cursor;

        END IF;

    DBMS_OUTPUT.PUT_LINE(emp_record.ename||emp_record.sal);

    END LOOP;

    COMMIT;

END;

/

删除

DECLARE

v_deptno EMP.deptno%TYPE:=30;

v_empno EMP.empno%TYPE;

v_job EMP.job%TYPE;

v_sal EMP.SAL%TYPE;

CURSOR emp_cursor IS SELECT ename,job,sal FROM EMP

WHERE deptno=v_deptno FOR UPDATE OF sal;

BEGIN

    FOR emp_record IN emp_cursor LOOP

        IF emp_record.sal<800 THEN

            DELETE FROM EMP WHERE CURRENT OF emp_cursor;

        END IF;

    DBMS_OUTPUT.PUT_LINE(emp_record.ename||emp_record.sal);

    END LOOP;

    COMMIT;

END;

/

 

 

 

 

 

 

 

Eg:为职工增加10%的工资,从最低工资开始长,增加后工资总额限制在50万以内

DECLARE

emp_num number:=0;

s_sal EMP.sal%TYPE;

e_sal EMP.sal%TYPE;

e_empno EMP.empno%TYPE;

CURSOR c1 IS SELECT empno,sal FROM emp

ORDER BY sal FOR UPDATE OF sal;

BEGIN

    OPEN c1;

    SELECT sum(sal) INTO s_sal FROM emp;

    LOOP

        FETCH c1 INTO e_empno,e_sal;

        EXIT WHEN c1%NOTFOUND;

        s_sal:=s_sal+e_sal*0.1;

        IF s_sal>=500000 THEN

        EXIT;

        END IF;

        UPDATE emp SET sal=sal*1.1 WHERE CURRENT OF c1;

        emp_num:=emp_num+1;

    END LOOP;

    CLOSE c1;

    INSERT INTO msg VALUES(emp_num,s_sal);

    COMMIT;

END;

/

 

 

带参数的游标

此参数只能在游标的查询语句中使用,只能向游标传递参数数值,不能通过参数带出结果

,可以有选择地给参数提供一个默认值。

 

Eg:首先查询DEPT表取出所有的部门号,然后根据DEPT表中返回的每一个部门号,从emp表查询该部门的雇员信息

DECLARE

CURSOR dept_cursor IS SELECT deptno FROM dept;

v_ename EMP.ename%TYPE;

v_sal EMP.sal%TYPE;

CURSOR emp_cursor(v_deptno NUMBER) IS

SELECT ename,sal FROM EMP WHERE deptno=v_deptno;

BEGIN

    FOR dept_record IN dept_cursor LOOP

        EXIT WHEN dept_cursor%NOTFOUND;

        OPEN emp_cursor(dept_record.deptno);

        LOOP

            FETCH emp_cursor INTO v_ename,v_sal;

            EXIT WHEN emp_cursor%NOTFOUND;

            DBMS_OUTPUT.PUT_LINE(dept_record.deptno||''||''||v_sal);

        END LOOP;

        CLOSE emp_cursor;

    END LOOP;

    COMMIT;

END;

/

隐式游标

由系统定义,不需要用户定义

用来处理INSERT,UPDATE,DELETE和单行的SELECT…INTO语句

游标名为SQL

游标的属性存储有关最近一次SQL命令的状态信息

隐式游标的游标属性%ISOPEN总是FALSE,因为当语句执行完后立即关闭隐式游标

SELEC….INTO语句只能执一行

 

 

存储过程

CREATE [OR REPLACE] PROCEDURE 过程名

[(参数名 [ IN | OUT | IN OUT ] 数据类型,... )]

{IS | AS}

            [说明部分]

BEGIN

            语句序列

    [EXCEPTION 出错处理]

END [过程名];

OR REPLACE 是一个可选的关键字,表示替代原有的过程

IS或AS后面是一个完整的PL/SQL块的三部分(说明部分,执行部分,异常处理部分)

IN表示输入变量,OUT表示输出变量,IN OUT表示输入输出变量,缺省表示IN

 

 

 

 

 

 

 

 

CREATE OR REPLACE

PROCEDURE modetest(p_inpara IN NUMBER,

                 p_outpara OUT NUMBER,

                 p_inoutpara IN OUT NUMBER)

AS

v_local NUMBER;

BEGIN

    v_local:=p_inpara;/*IN类型参数不能出现在:=的左边*/

p_outpara:=7;/*OUT类型参数不能出现在:=的右边*/

    v_local:=p_inoutpara;

    p_inoutpara:=7;

END;

CREATE OR REPLACE

PROCEDURE raise_salary(emp_id INTEGER,v_increase INTEGER)

AS /* 缺省IN */

BEGIN

     UPDATE EMP SET sal=sal+v_increase WHERE empno=emp_id;

     COMMIT;

END;

 

 

函数

参数都是IN类型,存储函数必须返回并且只返回一个结果,函数体的可执行部分必须有RETURN语句(RETURN 表达式).表达式的数值类型与RETURN子句定义要一致

 

CREATE OR REPLACE

FUNCTION get_sal(p_emp_no IN EMP.empno%TYPE)

RETURN NUMBER

AS

    v_emp_sal EMP.sal%TYPE:=0;

BEGIN

     SELECT sal INTO v_emp_sal FROM emp WHERE empno=p_emp_no;

     RETURN(v_emp_sal);

EXCEPTION

     WHEN no_data_found or too_many_rows THEN

             DBMS_OUTPUT.PUT_LINE('System Error');

     WHEN others THEN

                DBMS_OUTPUT.PUT_LINE(sqlerrm);

END;

 

CREATE OR REPLACE

FUNCTION average_sal(v_n IN NUMBER)

RETURN NUMBER

AS

CURSOR c_emp IS SELECT empno,sal FROM emp;

v_total_sal EMP.sal%TYPE:=0;

v_counter NUMBER;

v_emp_no EMP.EMPNO%TYPE;

BEGIN

    FOR r_emp IN c_emp LOOP

        EXIT WHEN c_emp%ROWCOUNT>v_n OR c_emp%NOTFOUND;

        v_total_sal:=v_total_sal+r_emp.sal;

        v_counter:=c_emp%ROWCOUNT;

        v_emp_no:=r_emp.empno;

        DBMS_OUTPUT.PUT_LINE('loop='||v_counter||';empno='||v_emp_no);

    END LOOP;

        DBMS_OUTPUT.PUT_LINE(v_total_sal);

    RETURN(v_total_sal/v_counter);

END average_sal;

 

 

过程和函数

参数类型不同:函数只有IN类型参数,而存储有IN,OUT,IN OUT三个类型参数

返回值的方法不同:函数返回只有一个值,而存储过程返回值由OUT参数带出来

调用方法不同:

过程(实际参数1,实际参数2……);

变量名:=函数名(实际参数1,实际参数2….);

 

过程/函数中的异常处理

CREATE OR REPLACE

PROCEDURE fire_emp(p_emp_no IN EMP.EMPNO%TYPE)

AS

invalid_employee EXCEPTION;/*定义错误*/

BEGIN

    DELETE FROM emp WHERE empno=p_emp_no;

    IF SQL%NOTFOUND THEN

        RAISE invalid_employee;/*触发错误*/

    END IF;

    EXCEPTION

        WHEN invalid_employee THEN

            ROLLBACK;

        WHEN others THEN

            DBMS_OUTPUT.PUT_LINE(sqlerrm);

END fire_emp;

 

 

在SQL*Plus中使用过程/函数

在SQL*Plus中,用VARIABLE定义的变量在引用时,必须前面加冒号(:)。用ACCEPT接受的变量在引用时,前面加&符号。

SET SERVEROUTPUT ON

ACCEPT p_emp_no PROMPT 'please enter the employee number:'

VARIABLE v_emp_name varchar2(14);

EXECUTE query_emp(&p_emp_no,:v_emp_name);

 

 

包是一个可以将相关对象存储在一起的PL/SQL结构

它包含了两个分离的组成部分:包说明和包主体

包的组成

可以将相关的若干程序单元组织到一块,用一个包名来标识这个集合

包中可以包含的程序单元

过程,函数,变量,游标,类型,常量,出错情况

包头

CREATE [OR REPLACE] PACKAGE 包名

{IS | AS}

公共变量的定义        | 

公共类型的定义        |

公共出错处理的定义    |

公共游标的定义        |

函数说明            |

过程说明      

END;

/

包体

CREATE [OR REPLACE] PACKAGE BODY 包名

{IS | AS}

私有变量的定义        | 

私有类型的定义        |

私有出错处理的定义    |

私有游标的定义        |

函数定义            |

过程定义      

END ;

/

CREATE OR REPLACE

PACKAGE SAL_PACKAGE AS

    PROCEDURE RAISE_SAL(V_EMPNO IN NUMBER, V_SAL_INCREMENT IN NUMBER);

    PROCEDURE REDUCE_SAL(V_EMPNO IN NUMBER, V_SAL_REDUCE IN NUMBER);

    v_raise_sal EMP.sal%TYPE:=0;

    v_reduce_sal EMP.sal%TYPE:=0;

end;

CREATE OR REPLACE

PACKAGE BODY SAL_PACKAGE AS

PROCEDURE RAISE_SAL(V_EMPNO IN NUMBER, V_SAL_INCREMENT IN NUMBER) AS

BEGIN

        UPDATE EMP SET EMP.SAL=SAL+V_SAL_INCREMENT WHERE EMP.EMPNO=V_EMPNO;

        COMMIT WORK;

        v_raise_sal:=v_raise_sal+v_sal_increment;

END;

 

PROCEDURE REDUCE_SAL(V_EMPNO IN NUMBER, V_SAL_REDUCE IN NUMBER) AS

BEGIN

        UPDATE emp SET sal=sal-v_sal_reduce WHERE empno=v_empno;

        COMMIT WORK;

        v_reduce_sal:=v_reduce_sal+v_sal_reduce;

END;

END;

所有提供的Package由SYS所拥有,对于不是SYS的用户,必须拥有EXCEUTE权限才能调用

所有Oracle提供的程序包都是以DBMS_或UTL_开头

 

 

静态SQL
静态SQL指直接嵌入在PL/SQL块中的SQL语句,静态SQL用于完成特定或固定的任务。
select sal from emp where empno=4000;

 

动态SQL
动态SQL运行PL/SQL块时动态输入的SQL语句。如果在PL/SQL需要执行DDL语句,DCL语句,或则需要执行更加灵活的SQL语句(select中有不同where条件),需要用到用到动态SQL。
编写动态SQL语句时,需要将SQL语句存放到字符串变量中,而且SQL语句可以包含占位符(以冒号开始)。
v_sql varchar2(100);
v_sql:='delete from emp where empno =:v_empno';

 

存储过程里面, 一般只写 DML的语句。
也就是基本的 SELECT . INSERT, DELETE, UPDATE 这一类的语句。
如果你要在存储过程里面, 执行 DDL 语句。
也就是 CREATE, ALTER, DROP 这一类的语句。
那么需要使用动态 SQL 来处理。
也就是
EXECUTE IMMEDIATE ( 'CREATE TABLE test( id int ) ' );
EXECUTE IMMEDIATE ( 'DROP TABLE test ' );
这样的方式来处理。
对于 DDL 语句, 这类语句, 如果成功了, 就生效了, 不能回滚的。
也就是
当你 Drop Table 某个表的时候, 执行成功了, 表就被删除了。
不需要你 Commit 的。
你也无法  Rollback , 然这个表不删除了。

简单来说 就是你一个存储过程当中 创建了一个表 table_a 然后要用insert into将其他的数据插入到这个table_a当中,但是因为你在创建过程的时候 table_a还不存在,过程就会显示有编译错误,因为table_a不存在必然导致过程无法执行,所以无法编译成功,而把insert into语句加如到 execute immediate之后 则oracle不会再去理会这个对象是否存在,因此可以成功编译和执行。

 

 

 

execute immediate

删除表

CREATE OR REPLACE

PROCEDURE PRO_DROP_TABLE(v_table_name VARCHAR2) AS

v_sql VARCHAR2(100);

BEGIN

    v_sql:='drop table '||v_table_name;

    execute immediate v_sql;

END;

 

BEGIN

FOR I IN 1 .. 100 LOOP

BEGIN

EXECUTE IMMEDIATE 'DROP TABLE T'||I;

EXCEPTION

WHEN OTHERS THEN NULL;

END;

EXECUTE IMMEDIATE 'CREATE TABLE T'||I||' ( DUMMY CHAR(1) )';

EXECUTE IMMEDIATE 'INSERT INTO T'||I||' VALUES ( ''X'' )';

END LOOP;

END;

/

 

 

dbms_utility的使用

将字符串每个逗号字符用方括号进行封装

dbms_utility.comma_to_table的使用

 

 

 

 

DECLARE

t_varrarray DBMS_UTILITY.LNAME_ARRAY;

vc_stringlist VARCHAR2(4000);

n_idx binary_integer;

BEGIN

    --comma to TABLE

    vc_stringlist:='dkf,dddl,fewe,klkj';

    DBMS_UTILITY.COMMA_TO_TABLE(vc_stringlist,n_idx,t_varrarray);

    DBMS_OUTPUT.PUT_LINE('TOTAL NUM: '||TO_CHAR(n_idx));

    FOR i IN 1..n_idx LOOP

        DBMS_OUTPUT.PUT_LINE(t_varrarray(i));

        t_varrarray(i):='['||t_varrarray(i)||']';

    END LOOP;

    DBMS_UTILITY.TABLE_TO_COMMA(t_varrarray,n_idx,vc_stringlist);

    DBMS_OUTPUT.PUT_LINE('');

    DBMS_OUTPUT.PUT_LINE(vc_stringlist);

END;

/

 

DECLARE

l_temp NUMBER;

BEGIN

    l_temp:=DBMS_UTILITY.get_time;

    DBMS_OUTPUT.PUT_LINE('before='||l_temp);

    DBMS_LOCK.SLEEP(5);

    l_temp:=DBMS_UTILITY.get_time;

    DBMS_OUTPUT.PUT_LINE('after='||l_temp);

END;

/

触发器

触发器由说明部分,语句执行部分和出错处理部分三部分组成的PL/SQL有名块(类似于存储过程和函数)

触发器不接受参数,不能再程序中调用

当触发事件发生时隐式地(自动地)执行

触发事件包括:在数据库中执行如下操作

INSERT

UPDATE

DELETE

 

将超过某工种工资范围的员工信息纪录到aduit_message表中。

sal_guide表纪录了每一工种的工资范围

CREATE OR REPLACE TRIGGER check_sal

BEFORE INSERT OR UPDATE OF sal,job ON emp FOR EACH ROW

WHEN (new.job <> 'PRESIDENT')

DECLAER

v_minsal sal_guide.minsal%TYPE;

v_maxsal sal_guide.maxsal%TYPE;

e_sal_out_of_range EXCEPTION;

BEGIN

SELECT minsal , maxsal INTO v_minsal , v_maxsal

FROM sal_guide WHERE job = :new.job;

IF (:new.sal < v_minsal) OR (:new.sal > v_maxsal) THEN

RAISE e_sal_out_of_range;

END IF;

EXCEPTION

WHEN e_sal_out_range THEN

INSERT INTO audit_message(line_nr,line)

VALUES(1,'salary'||to_char(:new.sal)||

'is out of range for employee'||to_char(:new.empno));

END ;

 

 

 

 

 

 

 

触发器类型:语句级和行级

触发事件:表的插入,更新,删除

触发时间:BEFORE和AFTER

触发器分语句级和行级触发器两个级别

行级触发器与语句级触发器的区别主要在于触发次数不同

如果DML语句只影响一行,则语句级和行级触发器效果一样

如果该DML语句影响多行,则行级触发器触发的次数比语句级触发器触发的次数多

语句之前触发->行之前触发->插入、更新、删除一条记录->行之后出发->语句之后触发

在触发器体内禁止使用COMMIT,ROLLBACK语句

事件指明触发事件的数据操纵语句,有三种可能的值:INSERT,UPDATE,UPDATE OF 列名1,DELETE

该触发器在一个数据操作语句发生时只触发一次

CREATE [OR REPLACE] TRIGGER 触发器名

{BEFORE | AFTER} 事件1 [OR 事件2 ...] ON 表名

PL/SQL 块;

 

 

Eg:创建一个BEFORE型语句级触发器。限制一周内向emp表插入数据的时间,如果是周六、周日,或晚上6点到第二天早上8点之间插入,则中断操作,并提示用户不允许在此时间向emp表插入

CREATE OR REPLACE TRIGGER secure_emp

BEFORE INSERT ON EMP

BEGIN

    IF(TO_CHAR(sysdate,'DY') IN ('SAT','SUN')

OR (TO_CHAR(sysdate,'HH24') NOT BETWEEN '8' AND '18'))

    THEN RAISE_APPLICATION_ERROR(-20500,'you may only insert emp during normal hours.');

    END IF;

END;

/

 

 

使用触发谓词(INSERTING,UPDATING,DELETING)

触发器可以包含多个触发事件,在触发器中使用谓词判断是哪个触发了触发器

谓词 行为和值

INSERTING            如果触发事件是INSERTING,则谓词的值为TRUE,否则为FALSE

 

 

 

CREATE OR REPLACE TRIGGER secure_emp

BEFORE DELETE OR INSERT OR UPDATE ON EMP

BEGIN

    IF(TO_CHAR(sysdate,'DY') IN ('SAT','SUN') OR

        (TO_CHAR(sysdate,'HH24') NOT BETWEEN '8' AND '18')) THEN

        IF DELETING THEN

            RAISE_APPLICATION_ERROR(-20502,'you may only delete emp during normal hourse');

        ELSE INSERTING THEN

            RAISE_APPLICATION_ERROR(-20502,'you may only insert emp during normal hourse');

        ELSE THEN

            RAISE_APPLICATION_ERROR(-20504,'you may only update emp during normal hours.');

        END IF;

END IF;

END;

 

通过在CREATE TRIGGER 语句中指定FOR EACH ROW 子句创建一个行触发器

CREATE [OR REPLACE] TRIGGER 触发器名

{BEFORE | AFTER} 事件1 [OR 事件2 ...] ON 表名

FOR EACH ROW [WHEN 限制条件]

PL/SQL 块;

 

Eg:将每个用户对数据库emp表进行数据操纵(插入、更新、删除)的次数纪录到audit_table表中

CREATE OR REPLACE TRIGGER audit_emp

AFTER DELETE OR INSERT OR UPDATE ON emp FOR EACH ROW

BEGIN

IF DELETING THEN

UPDATE audit_table SET del = del + 1

WHERE user_name = user AND table_name = 'emp'

AND colun_name IS NULL;

ELSIF INSERTING THEN

UPDATE audit_table SET ins = ins + 1

WHERE user_name = user AND table_name = 'emp'

AND colun_name IS NULL;

ELSE THEN

UPDATE audit_table SET upd = upd + 1

WHERE user_name = user AND table_name = 'emp'

AND colun_name IS NULL;

END IF;

END;     

 

使用行级触发器的标识符(:OLD和:NEW)

在行级触发器中,列名前加上:OLD标识符表示该列变化前的值,加上:NEW标识符表示变化后的值

在BEFORE型行级触发器和AFTER型行级触发器中使用这些标识符

在语句级触发器中不要使用这些标识符

在触发器体的SQL语句或PL/SQL语句中使用这些标识符时,前加冒号(:)

在行级触发器的WHEN限制条件中使用这些标识符时,前面不要加冒号(:)

 

Eg:在行级触发器中获取某列的新值和旧值,为emp表中的所有数据保留一个历史档案

 

 

 

CREATE OR REPLACE TRIGGER audit_emp_values

AFTER DELETE OR INSERT OR UPDATE ON emp FOR EACH ROW

BEGIN

    INSERT INTO audit_emp(user_name,timestamp,empno,old_ename,

new_ename,old_job,new_job,old_sal,new_sal)

    VALUES(USER,SYSDATE,:old.empno,:old.ename,:new.ename,:old.job,

:new.job,:old.sal,:new.sal);

END;

 

Eg:在行级触发器加WHEN限制条件。根据销售员工资的改变自动计算销售员的奖金

 

CREATE OR REPLACE TRIGGER derive_comm

BEFORE UPDATE OF sal ON emp FOR EACH ROW

WHEN (new.job = 'SALESMAN')

BEGIN

:new.comm := :old.comm * (:new.sal/:old.sal);

END;        

 

 

 

 

INSTEAD OF 触发器

instead of trigger 是基于视图建立的,不能建在表上,为什么要建在视图上,一般的视图如果其数据来源一个表并且包含该表的主键,就可以对视图进行DML操作.另外一种情况是 从多个表查询出来的.这样我们就不能对视图进行操作了,也就是只能查询.instead of trigger可以解决建在多表上视图的更新操作.

 

Create Or Replace Trigger Trg_InsUpdDel_t
Instead Of Insert or update or delete on t for each row
begin
    If Inserting Then
        Insert Into t1 (t11,t12) Values (:New.f1,:New.f2);
        Insert Into t2 (t11,t22) Values (:New.f1,:New.f3);
    elsif Updating Then
        Update t1 set t11=:New.f1,t12=:New.f2 where t11=:New.f1;
        Update t2 set t11=:New.f1,t22=:New.f3 where t11=:New.f1;
    elsif Deleting then
        Delete from t1 where t11=:Old.f1;
        Delete from t2 where t11=:Old.f1;
    End if;
end;

 

 

 

BULK DML

Oracle引入了两个DML语句: BUKL COLLECT 和 FORALL

这两个语句在PL/SQL内部进行以一种数据处理

BUKL COLLECT 提供对数据的高速检索

FORALL 可大大改进INSERT,UPDATE和DELETE操作的性能

Oracle数据库使用这些语句大大减少了PL/SQL与SQL语句执行引擎的环境切换次数,从而使其性能有了显著提高

DECLARE

    TYPE books_aat IS TABLE OF book%ROWTYPE

        INDEX BY PLS_INTEGER;

    my_books books_aat;

BEGIN

    SELECT * BULK COLLECT INTO my_books

    FROM book WHERE title LIKE '%PL/SQL%';

    ...

END;

 

CREATE TYPE books_nt
IS TABLE OF book%ROWTYPE;

CREATE OR REPLACE PROCEDURE add_books ( books_in IN books_nt)
IS
BEGIN
    FORALL book_index
        IN books_in.FIRST .. books_in.LAST
    INSERT INTO book VALUES books_in(book_index);
    ...
END;

什么时候使用FORALL

FORALL可以大量的提高从源表或者视图到目标表的插入,更新,删除,合并操作

 

如果FORALL的调用集合中缺失一个元素那么错误将会发生

如果一个行级别的发生那么整个进程将会停止

DECLARE

Type region_id_tbl IS TABLE of NUMBER INDEX BY BINARY_INTEGER;

Type region_name_tbl IS TABLE of VARCHAR2(20) INDEX BY BINARY_INTEGER;

region_ids region_id_tbl;

region_names region_name_tbl;

ret_code NUMBER;

ret_errmsg VARCHAR2(1000);

Procedure load_regions_bulk_bind (region_ids IN region_id_tbl,

region_names IN region_name_tbl, retcd OUT NUMBER, errmsg OUT VARCHAR2)

IS

BEGIN

-- clean up the region_tab table initially.

DELETE FROM region_tab;

FORALL i IN region_ids.FIRST..region_ids.LAST

INSERT INTO region_tab values (region_ids(i), region_names(i));

Retcd := 0;

EXCEPTION

WHEN OTHERS THEN

COMMIT;

Retcd := SQLCODE;

Errmsg := SQLERRM;

END;

BEGIN

FOR i IN 1..5 LOOP

Region_ids(i) := i;

Region_names(i) := 'REGION'||i;

END LOOP;

Load_regions_bulk_bind(region_ids, region_names, ret_code, ret_errmsg);

EXCEPTION WHEN OTHERS THEN

RAISE_APPLICATION_ERROR(-20112, SQLERRM);

END;

 

DECLARE

t_outtab_type IS TABLE OF input_table%ROWTYPE;

v_outtab t_outtab_type;

BEGIN

SELECT * BULK COLLECT INTO v_outtab

FROM input_table

WHERE key_col = '<val1>';

 

FOR i IN 1..v_outtab.COUNT LOOP

-- Process the rows one by one according to required logic.

END LOOP;

END;

/

 

DECLARE

t_outtab_type IS TABLE OF input_table%ROWTYPE;

v_outtab t_outtab_type;

CURSOR c_in IS SELECT * FROM input_table

WHERE key_col = '<val1>';

BEGIN

OPEN c_in;

FETCH c_in BULK COLLECT INTO v_outtab;

FOR i IN 1..v_outtab.COUNT LOOP

-- Process the rows one by one according to required logic.

END LOOP;

 

CLOSE c_in;

END;

/

DECLARE

t_outtab_type IS TABLE OF input_table%ROWTYPE;

v_outtab t_outtab_type;

CURSOR c_in IS SELECT * FROM input_table

WHERE key_col = '<val1>';

BEGIN

OPEN c_in;

FETCH c_in BULK COLLECT INTO v_outtab;

FOR i IN 1..v_outtab.COUNT LOOP

-- Process the rows one by one according to required logic.

END LOOP;

 

CLOSE c_in;

END;

/

 

 

用bulk query的主要有点,

减少运行的时间,更少的延时

如果取得的数据很大的话,那么回增加内存的消耗

 

可以通过特定的limite数量来控制取得的数量

DECLARE

t_outtab_type IS TABLE OF input_table%ROWTYPE;

v_outtab t_outtab_type;

CURSOR c_in IS SELECT * FROM input_table WHERE key_col = '<val1>';

BEGIN

OPEN c_in;

LOOP

FETCH c_in BULK COLLECT INTO v_outtab LIMIT 100;

FOR i IN 1..v_outtab.COUNT LOOP

-- Process the rows one by one according to required logic.

NULL;

END LOOP;

EXIT WHEN c_in%NOTFOUND;

END LOOP;

CLOSE c_in;

END;

/

 

自治事物

自治事物允许你在一个事务中创建一个事务,这个事务将会独立于父事务进行提交,回滚等改变

允许你挂起现在正在执行的事务,开始一个新的事务,做一些工作,提交回滚,所有的这些事情不会影响当前正在执行事务的状态

通过pragma autonomous_transaction将一个pl/sql程序结构设定为自治事务,pragma是编译器指令,可以将procedure function package等顶级匿名块定义成自治的程序结构。

create or replace trigger EMP_AUDIT

before update on emp for each row

declare

    pragma autonomous_transaction;

    l_cnt number;

begin

    select count(*) into l_cnt from dual

    where EXISTS ( select null from emp where empno = :new.empno

        start with mgr = ( select empno from emp where ename = USER )

        connect by prior empno = mgr );

    if ( l_cnt = 0 ) then

        insert into audit_tab ( msg )

        values ( 'Attempt to update ' || :new.empno );

        commit;

        raise_application_error( -20001, 'Access Denied' );

    end if;

end;

/

 

 

 

绑定变量

下面的代码每次都要硬编码

动态SQL

create or replace procedure dsal(p_empno in number)
as
begin
    execute immediate
        'update emp set sal = sal*2

        where empno = '||p_empno;
    commit;
end;

 

通过如下改变就可以了

绑定变量

create or replace procedure dsal(p_empno in number)
as
begin
    execute immediate
        'update emp set sal = sal*2

        where empno = :x' using p_empno;
    commit;
end;

 

游标变量 : 声明游标实际上是创建一个指针 , 指针具有数据类型 REF X.REF 是 REFERENCE ,X 是表示类对象 . 因此 , 游标变量具有数据类型 REF  CURSOR.

注 : 游标总是指向相同的查询工作区 , 游标变量能够指向不同的工作区 , 因此游标和游标变量不能互操作 .

 

DECLARE

TYPE empcurtyp IS REF CURSOR RETURN employees%ROWTYPE;

emp empcurtyp;

-- after result set is built, process all the rows inside a single procedure

-- rather than calling a procedure for each row

PROCEDURE process_emp_cv (emp_cv IN empcurtyp)

IS

person employees%ROWTYPE;

BEGIN

DBMS_OUTPUT.PUT_LINE('-----');

DBMS_OUTPUT.PUT_LINE('Here are the names from the result set:');

LOOP

FETCH emp_cv INTO person;

EXIT WHEN emp_cv%NOTFOUND;

DBMS_OUTPUT.PUT_LINE('Name = ' || person.first_name || ' ' || person.last_name);

END LOOP;

END;

BEGIN

-- First find 10 arbitrary employees.

OPEN emp FOR SELECT * FROM employees WHERE ROWNUM < 11;

process_emp_cv(emp);

CLOSE emp;

 

-- find employees matching a condition.

OPEN emp FOR SELECT * FROM employees WHERE last_name LIKE 'R%';

process_emp_cv(emp);

CLOSE emp;

END;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值