`
zjm16
  • 浏览: 71046 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
文章分类
社区版块
存档分类
最新评论

ORACLE存储过程实战 入门、进阶(转)

阅读更多
入门篇
1.基本结构
CREATE OR REPLACE PROCEDURE 存储过程名字
(
    参数1 IN NUMBER,
    参数2 IN NUMBER,
    参数3 OUT NUMBER
) IS
变量1 INTEGER := 默认值;
变量2 DATE;
BEGIN
...
EXCEPTION
  WHEN 异常类别 THEN
      ...
  WHEN OTHERS THEN
    ...
END 存储过程名字;

2.基本操作
变量赋值 V_TEST := 1;
动态赋值 SELECT COL1,COL2,... INTO VAR1,VAR2,... FROM ... WHERE ...;
字符相加  'STRING1' || 'STRING2';
相等判断 =而不是==;
逻辑判断 AND,OR,>,<,<>;
打印输出 dbms_output.put_line();
是否为空 V_TEST IS (NOT) NULL;
提交回滚 COMMIT/ROLLBACK;
异常捕捉 BEGIN ... EXCEPTION WHEN ... THEN ... END;

3.IF 判断
  IF (判断条件) THEN
    BEGIN
       ...
    END;
  ELSIF THEN
   ...
  ELSE
   ...
  END IF;

4.WHILE 循环
  WHILE (判断条件) LOOP
   BEGIN
   ...
   END;
  END LOOP;

5.FOR 循环
FOR X IN (SELECT col1,col2 ...) LOOP
  BEGIN
   引用X.col1、X.col2
   ...
  END;
END LOOP;

6.常见FUNCTION
以下列出的是一些常用函数的普通用法,并不是按照ORACLE函数标准进行说明的,所以有些用法并不全面,只是列举了最常用的情况;
SYSDATE:用来得到系统的当前日期;
TO_NUMBER(STR):将字符串转换成数字;若转换不成功,则抛错:ORA-01722: invalid number;
TO_CHAR(?):?可支持很多的类型,比如NUM,DATE……;DATE用得比较多,一般来说有如下用法:
  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'):将当前日期按照格式输出完整字符串;其中的日期格式可以自行定义;
TO_DATE(STR,FORMAT):对字符串按照一定格式解析成Date型;
NVL(col,defaultValue):对col判断是否为NULL,若为NULL,则返回defaultValue;
CONCAT(STR1,STR2):连接两个字符串,功能与||相同;
LENGTH(STR):返回字符串的长度,其中中文字符的长度计算要视DB的编码而定,在GBK编码下,中文也算1个字符;
  若需要中文字符长度算2个字符,则可以用LENGTHB(STR)代替;
  简单来说,LENGTH返回字符长度;LENGTHB返回字节长度;
SUBSTR(str,start,count):截取子字符串,从start开始,取count个;
  start为0和1的效果是一样的,都是从str的第一个字符开始截取;
  若start>=LENGTH(str),直接返回空,不会抛错;
INSTR(STR1,STR2,I,J):在一个字符串中搜索指定的字符,返回发现指定的字符的位置;
  STR1:被搜索的字符串
  STR2:希望搜索的字符串
  I:搜索的开始位置,默认为1
  J:第几次出现的位置,默认为1
LOWER/UPPER(STR):将字符串全部转成小写或者大写;在GBK编码下,该函数对中文无效;
LTRIM/RTRIM(STR):删除左边货右边出现的空格字符串;
FLOOR(NUMBER):对给定的数字取整数;
MOD(num1,num2):返回num1除以num2的余数;num1和num2可以带有小数位;返回的值也可能有小数位;
ROUND(num):对num四舍五入取值;
TRUNC(num):对num截取整数,去掉小数位,不进行四舍五入;功能与FLOOR类似;当然也可以在参数中指定精度;
TRUNC(Date):对date截取到天,去掉小时分钟之类的,其返回类型仍然为Date;类似的,也可以在参数中指定Date截取级别,比如'hh';
ADD_MONTHS(Date,num):对Date加上num个月,num可以为负数,表示减去几个月;
  num可以有小数位,但是并不会起效,效果相当于ADD_MONTHS(Date,TRUNC(num));
CHARTOROWID/ROWIDTOCHAR:将字符数据类型转换为ROWID类型或者相反;
AVG/MIN/MAX/SUM/COUNT(DISTINCT|ALL col):对某字段取平均、最小、最大、求和、计数;默认为ALL,若需要对不重复值运算,则用DISTINCT参数;
CASE WHEN ... THEN ...
    WHEN ... THEN ...
    ELSE ...
END:根据不同条件取不同的值,与JAVA中的CASE也很类似;

游标篇

1.CURSOR 申明
无参数申明:CURSOR CUR_NAME IS SELECT COL1,COL2,... FROM ... WHERE ...;
带参数申明:CURSOR CUR_NAME(PARAM1 DATA_TYPE,PARAM2 DATA_TYPE,...) IS SELECT COL1,COL2,... FROM ... WHERE ...;

2.用FOR IN使用CURSOR
  FOR CUR_RS IN CUR_NAME LOOP
   BEGIN
    引用CUR_RS.COL1,CUR_RS.COL2,...
    ...
   END;
END LOOP;

3.用FETCH INTO使用CURSOR
  LOOP
   FETCH CUR_NAME INTO V_COL1,V_COL2,...;
   ...
   EXIT WHEN CUR_NAME%NOTFOUND;
  END LOOP;
 
4.用BULK COLLECT使用CURSOR,用于批量操作,提高效率
--申明TABLE数据类别
TYPE TYPE_NAME IS TABLE OF DATA_TYPE;
比如TYPE T_ROWID IS TABLE OF UROWID;

--申明变量
VAR_NAME TYPE_NAME;
比如VAR_ROWID T_ROWID;

LOOP
  --1000条一个批次
   FETCH CUR_NAME BULK COLLECT INTO VAR_ROWID,V_COL2,... LIMIT 1000;
    FORALL i IN VAR_ROWID.FIRST .. VAR_ROWID.LAST
     ...ONE SQL;
    COMMIT;
   EXIT WHEN CUR_NAME%NOTFOUND;
  END LOOP;

5.EXIT WHEN CUR_NAME%NOTFOUND 位置
在配合FETCH INTO语句使用时,EXIT WHEN CUR_NAME%NOTFOUND可以放在循环的开始处,也可以放在最后面;视具体场景而定;
WHEN CUR_NAME%NOTFOUND也可以用其他的判断条件替换;
比如在用BULK COLLECT时,就可以用EXIT WHEN VAR_ROWID.COUNT = 0;不过此时需要放在FETCH之后FORALL之前;

进阶篇

1.SELECT INTO语句要求SELECT出来的RESULT至少有一条记录;
若RS为NULL,则会抛出NO_DATA_FOUND的EXCEPTION;所以需要进行异常捕获;

2.进行UPDATE、DELETE操作时可以用ROWID替换PK可以提高执行效率;
因为ROWID是直接定位到物理磁盘地址,而无需先从PK Index中查询,从而有效降低IO次数;
比如UPDATE MD_USER SET NAME = XX WHERE ROWID = ?;
不过用ROWID时需要小心,因为1)ROWID会被重用;2)在有GROUP BY语句的条件中无法使用;

3.EXCEPTION捕获之后若不需要做任何事情,则可以如下处理:
BEGIN ... EXCEPTION WHEN NO_DATA_FOUND THEN NULL; END;

4.EXCEPTION能够捕获的常见类别有:
NO_DATA_FOUND:代码块中某条SELECT INTO语句返回的RS为NULL;
TOO_MANG_ROWS:SELECT INTO语句符合条件的记录有多条返回;
DUP_VAL_ON_INDEX:对于数据库表中的某一列,该列已经被限制为唯一索引,程序试图存储两个重复的值;
VALUE_ERROR:在转换字符类型,截取或长度受限时,会发生该异常,如一个字符分配给一个变量,而该变量声明的长度比该字符短,就会引发该异常;
STORAGE_ERROR:内存溢出;
ZERO_DIVIDE:除数为零;
CASE_NOT_FOUND:对于选择case语句,没有与之相匹配的条件,同时,也没有else语句捕获其他的条件;
CURSOR_ALREADY_OPEN:程序试图打开一个已经打开的游标;
TIMEOUT_ON_RESOURCE:系统在等待某一资源,时间超时;
OTHERS:所有;
除此之外,还可以用RAISE来抛出一个自定义的EXCEPTION;比如
DECLARE MY_ERROR EXCEPTION;
BEGIN
  IF (...) THEN
   RAISE MY_ERROR;
  END IF;
EXCEPTION WHEN MY_ERROR THEN
  ...
  WHEN OTHERS THEN
  ...
END;

5.批量操作BULK COLLECT INTO情况下,FORALL下面只能有一条SQL操作,也不能是存过调用;若需要执行多条SQL,则需要用多个FORALL,比如
...
LOOP
  --1000条一个批次
   FETCH CUR_NAME BULK COLLECT INTO VAR_ROWID,V_COL2,... LIMIT 1000;
    FORALL i IN VAR_ROWID.FIRST .. VAR_ROWID.LAST
     INSERT INTO ...;
    FORALL i IN VAR_ROWID.FIRST .. VAR_ROWID.LAST
     UPDATE TABLE_NAME SET ...;
    COMMIT;
   EXIT WHEN CUR_NAME%NOTFOUND;
  END LOOP;

6.重复数据高效清理SQL:
DELETE FROM TABLE_NAME tn
     WHERE tn.ROWID >
           (SELECT MIN(x.ROWID)
              FROM TABLE_NAME x
             WHERE x.col1 = tn.col1
               AND x.col2 = tn.col2);
  --根据col1、col2两个字段清理,清理后两个字段满足unique约束;

7.一条SQL搞定如下场景:当数据存在进行更新,否则进行新增;
MERGE INTO TABLE_NAME tn
   USING (SELECT v_pk AS pk FROM DUAL) tmps
   ON (tn.pk = tmps.pk)
   WHEN MATCHED THEN
      UPDATE
         SET col1 = ?, col2 = ? ,...
   WHEN NOT MATCHED THEN
      INSERT (col1, col2, ...)
      VALUES (v_col1, v_col2, ...);
注意:ON子句中的字段不能在UPDATE子句中进行SET操作;

8.CONNECT BY语句使用
SQL> SELECT rownum rn FROM dual CONNECT BY rownum <= 5;

         RN
----------
          1
          2
          3
          4
          5
该语句一般配合笛卡尔积使用,一条语句搞定很多条INSERT语句的事情,可以大大减少SQL代码量;

LOCK篇

数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。

加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作。

在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。

根据保护的对象不同,Oracle数据库锁可以分为以下几大类:
DML锁(data locks,数据锁),用于保护数据的完整性;
DDL锁(dictionary locks,字典锁),用于保护数据库对象的结构,如表、索引等的结构定义;
内部锁和闩(internal locks and latches),保护数据库的内部结构,应用于SGA;

在我们实际应用开发中涉及较多的是DML锁,其他两种的话DBA会更加关心点;
DML锁的目的在于保证并发情况下的数据完整性,主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。

当Oracle执行DML语句时,系统自动在所要操作的表上申请TM类型的锁。当TM锁获得后,系统再自动申请TX类型的锁,并将实际锁定的数据行的锁标志位进行置位。这样在事务加锁前检查TX锁相容性时就不用再逐行检查锁标志,而只需检查TM锁模式的相容性即可,大大提高了系统的效率。TM锁包括了SS、SX、S、X等多种模式,在数据库中用0-6来表示。不同的SQL操作产生不同类型的TM锁。如下图所示:  值 锁模式 锁描述 SQL
0 NONE    
1 NULL 空 SELECT
2 SS(ROW-S) 行级共享锁
其他对象只能查询这些数据行 SELECT FOR UPDATE、LOCK FOR UPDATE、
LOCK ROW SHARE
3 SX(ROW-X) 行级排它锁
在提交前不允许做DML操作 INSERT、UPDATE、DELETE、
LOCK ROW SHARE
4 S(SHARE) 共享锁 CREATE INDEX、LOCK SHARE
5 SSX(S/ROW-X) 共享行级排它锁 LOCK SHARE ROW EXCLUSIVE
6 X(eXclusive) 排它锁 ALTER TABLE、DROP TABLE、DROP INDEX、
TRUNCATE TABLE、LOCK EXCLUSIVE


在数据行上只有X锁(排他锁)。在 Oracle数据库中,当一个事务首次发起一个DML语句时就获得一个TX锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行DML语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后,TX锁被释放,其他会话才可以加锁。

在大概了解oracle的锁机制之后,我们来解决几个基本的问题:
1.UPDATE/DELETE操作会将RS锁定,直至操作被COMMIT或者ROLLBACK;
若操作未COMMIT之前其他session对同样的RS做变更操作,则操作会被hold,直至前session的UPDATE/DELETE操作被COMMIT;

2.session内外SELECT的RS范围
前提:INSERT、UPDATE操作未COMMIT之前进行SELECT;
若在同一session内,SELECT出来的RS会包括之前INSERT、UPDATE影响的记录;
若不在同一session内,SELECT出来的RS不会包括未被COMMIT的记录;

3.SELECT.... FOR UPDATE [OF cols] [NOWAIT/WAIT] [SKIP LOCKED]
OF cols:只锁定指定字段所在表的RS,而没有指定的表则不会锁定,只会在多表联合查询时出现;
NOWAIT:语句不会hold,而是直接返回错误ORA-00054: resource busy and acquire with NOWAIT specified;
WAIT N:语句被hold N秒,之后返回错误ORA-30006: resource busy; acquire with WAIT timeout expired;
SKIP LOCKED:不提示错误,而是直接返回no rows selected;
以上几个选项可以联合使用的,比较推荐的有:
SELECT.... FOR UPDATE NOWAIT:对同一RS执行该SQL时,直接返回错误;
SELECT.... FOR UPDATE NOWAIT SKIP LOCKED:对同一RS执行该SQL时,直接返回空行;
PS:当RS被LOCK住之后,只对同样请求LOCK的语句有效,对无需LOCK的SELECT语句并没有任何影响;

hint篇

hint中我们最常用到的有ordered、use_nl、use_hash、index、full这五种;

下面就介绍下这5种hint适用的主要场合:

1)use_nl主要用于在多表join的时候,nl的意思是nest loop,就是嵌套查询;

         规则为,根据参数中指定表名的顺序,根据where子句中的查询条件把所有记录都查出来,然后再循环这些记录与另外的表join;

         适应场合为:某表的查询结果集很小,而其他表的查询结果集很大;

         比如:select a.* from table1 a,table2 b where a.xx='1' and b.yy = '2' and a.x=b.x

         若根据a.xx='1'从table1中查询出来的结果集比根据b.yy = '2'从table2中查询出来的结果集小得多,则可以加上use_nl(a b);

         使用的时候将小结果集放前面,大结果集放后面;

2)use_hash可以看作是use_nl的兄弟,也是用于在多表join的时候,但是他不使用嵌套,而是用hash join;

         其规则和use_nl完全一样;

         适应场合为:各表的查询结果集都比较大,而且大小差不多;

         比如:select a.* from table1 a,table2 b where a.xx='1' and b.yy = '2' and a.x=b.x

         若根据a.xx='1'从table1中查询出来的结果集和根据b.yy = '2'从table2中查询出来的结果集都比较大,而且大小差不多,则可以加上use_hash(a b);

3)ordered用于强制指定DB按照FROM子句中各表的先后顺序进行数据查询;

         比如说SELECT A.* FROM TABLE1 A, TABLE2 B WHERE A.X=B.X ……中添加了/*+ORDERED */,则标识oracle会强制先查询table1中的记录,然后再与table2 join;

         一般来说这个hint都是与use_nl和use_hash联合使用的;

         比如1)中的语句可以为select /*+ORDERED use_nl(a b) */ a.* from table1 a,table2 b where a.xx='1' and b.yy = '2' and a.x=b.x

4)index主要用于强制指定使用某个索引,一般选择效率更高的索引;

         适应场合为:where子句中有几个索引可供选择、或者是用到了联合索引中的第一个字段;

         比如select a.* from table1 where column1 = 'xx' and column2 = 'yy'

         若table1中column1和column2两个字段都有索引,则可以强制指定使用哪个索引,比如index(table1 table1_ind_column1)

5)full用于指定全表扫描,这个实际中应用的较少;

         按照DBA的说法,如果使用index查出来的结果集>=全表记录的25%,那么使用index的效率并不高,还不如用全表扫描;

分析函数篇

有时候我们需要从DB中提取一些很复杂的数据,而标准SQL却对此无能为力,或者是执行效率非常的低;比如我们需要提取如下数据:
    逐行显示各个部门的累计工资,每行包括部门内前面所有人的工资总和;
    查找各个部门工资最高的前N个人;
    ……

语法
Function名称([参数]) OVER ([partition 子句][ order 子句] [window 子句])
  OVER为分析函数的关键字,用于区别普通的聚合函数;从语法格式上区分的话,没加over()即时聚合函数,加了over()就是分析函数。
Partition 子句:Partition by exp1[ ,exp2]...;
  主要用于分组,可以理解成select中的group by;不过它跟select语句后跟的group by 子句并不冲突;指定该子句之后,前面的函数起效范围就是该分组内,若不指定,则Function的起效范围是全部结果集。
Order 子句:Order by exp1[asc|desc] [ ,exp2 [asc|desc]]... [nulls first|last];
  其参数基本与select中的order by相同;Nulls first|last是用来限定nulls在分组序列中的所在位置的,我们知道oracle中对于null的定义是未知,所以默认order by的时候nulls总会被排在最前面。如果想控制值为null的行显示位置,nulls first|last参数就能派上用场了。
Window 子句:该子句的语法比较复杂,具体可以见下图;

  该子句给出了一个定义变化或者固定的数据窗口方法,分析函数将对这些数据进行操作;默认情况下,一般用不上该子句,分析函数产生一个固定的窗口,影响的数据范围是从第一行到当前行,其效果和RANGE BETWEEN UNDOUNDED PRECEDING AND CURRENT ROW一样;若需要指定操作数据为当前行及其前两行,则可以用ROWS 2 PRECEDING来实现其效果;
其中用[]标注的子句都可以为空,一个最简单的分析函数可能是COUNT(*) OVER ();

样例
逐行显示各个部门的累计工资,每行包括部门内前面所有人的工资总和:
SELECT EMP_NO,
       NAME,
       DEPT_NO,
       SUM(SAL) OVER(PARTITION BY DEPT_NO ORDER BY EMP_NO) DEPT_SAL_SUM
  FROM EMP
ORDER BY DEPT_NO, EMP_NO;

查找各个部门工资最高的前N个人:
SELECT *
  FROM (SELECT DEPT_NO,
               NAME,
               SAL,
               DENSE_RANK() OVER(PARTITION BY DEPT_NO ORDER BY SAL DESC) DR
          FROM EMP)
WHERE DR <= 3
ORDER BY DEPT_NO, SAL DESC;

注意点
1、分析函数与聚合函数非常相似,不同于聚合函数的地方在于它们每个分组序列均返回多行,而聚合函数返回一行;
2、带有分析函数的SQL列表中,除了order by子句之外,分析函数将在SQL语句中最后执行;因此,分析函数只能用于select的列或order by子句,而不能用于where、group by、having之类的语句中;
3、当分析函数中使用了distinct参数时,则只能使用partition子句,而不能指定order by子句;
4、SELECT语句中的ORDER BY子句与分析函数中的order by子句是互不影响的,但一般来说两者一致比较好,若两者不一致,则意味着分析函数需要对结果集进行多次排序,这将严重降低分析函数的执行效率;
5、dense_rank在做排序时如果遇到列有重复值,则重复值所在行的序列值相同,而其后的序列值依旧递增,rank则是重复值所在行的序列值相同,但其后的序列值从+重复行数开始递增,而row_number则不管是否有重复行,序列值始终递增;

函数列表
大致有26个函数可用,其中很多都是和聚合函数同名的,比如SUM、AVG、MIN、MAX……;其他是一些提供新功能的新函数;具体的函数列表如下:

分享到:
评论

相关推荐

    深入浅出Oracle: DBA入门、进阶与诊断案例.pdf

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是一本专为数据库管理员(DBA)设计的Oracle技术指南。这本书详细介绍了Oracle数据库管理的基础知识,中级技能以及高级故障诊断技巧,旨在帮助读者从新手到专家逐步提升...

    深入浅出Oracle:DBA入门、进阶与诊断案例

    随着内容的深入,读者将进入Oracle技术的进阶领域,学习高级SQL查询技巧、索引优化、存储过程和触发器的编写、数据库性能调优等。这部分内容对于提升数据库的运行效率和稳定性至关重要。同时,还会涉及分布式数据库...

    《深入浅出Oracle:DBA入门、进阶与诊断案例》电子书

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是一本专为数据库管理员(DBA)设计的Oracle技术指南。这本书详细介绍了Oracle数据库管理的基础知识,中级技能以及高级故障诊断技巧,旨在帮助读者从新手到专家逐步提升...

    深入浅出Oracle:DBA入门、进阶与诊断案例.pdf

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是一本专为Oracle数据库管理员(DBA)设计的综合指南。这本书旨在帮助初学者快速掌握Oracle数据库的基础知识,并逐步提升到高级技能,同时提供了丰富的实战案例来帮助...

    深入浅出Oracle: DBA入门、进阶与诊断案例. .pdf

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是数据库领域的经典之作,由知名专家eagle精心编著,旨在帮助读者全面理解和掌握Oracle数据库管理(DBA)的各项技能。Oracle DBA是一个关键角色,负责维护Oracle数据库...

    深入浅出Oracle: DBA入门、进阶与诊断案例(原生PDF)

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是一本专为Oracle数据库管理员(DBA)设计的教程,旨在帮助读者从基础知识到高级技巧,全面掌握Oracle数据库的管理和维护。作者通过多年的实战经验,将理论知识与实际案例...

    深入解析Oracle--DBA入门、进阶与诊断案例

    在学习过程中,结合提供的《深入浅出Oracle.DBA入门.进阶与诊断案例》一书,通过实际操作来巩固理论知识,如创建并管理数据库对象,模拟性能问题并进行优化,模拟故障并进行恢复等。 总的来说,Oracle DBA的学习是...

    深入浅出Oracle: DBA入门、进阶与诊断案例. pdf

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是盖国强撰写的一本专为Oracle数据库管理员(DBA)设计的教程。这本书旨在帮助初学者掌握Oracle数据库的基本概念,同时也为有一定经验的DBA提供进一步提升技能和解决...

    深入浅出Oracle: DBA入门、进阶与诊断案例

    《深入浅出Oracle:DBA入门、进阶与诊断案例》是一本专为Oracle数据库管理员(DBA)设计的教程,旨在帮助初学者快速掌握Oracle基础,并进一步提升至高级技能,同时提供实战案例来帮助读者解决可能出现的问题。...

Global site tag (gtag.js) - Google Analytics