首页 > 数据库 > Oracle > 正文
Oracle数据库基础知识
2015-01-08       个评论    来源:中原  
收藏    我要投稿

一、 SQL基础知识

 

数据抽象:物理抽象、概念抽象、视图级抽象,内模式、模式、外模式

SQL语言包括数据定义、数据操纵(data manipulation),数据控制(data control)

数据定义:create table,alter table,drop table, craete/drop index等

数据操纵:select ,insert,update,delete,

数据控制:grant,revoke

创建、删除数据库

创建:create database database-name

删除:drop database dbname

创建、删除修改表

创建表:

综合评价:★★

create table tabname(col1 type1 [not null][primary key],col2 type2 [not null],..)

根据已有的表创建新表:

(1)建一个新表,架构、字段属性、约束条件、数据记录跟旧表完全一样:

create tabletab_new as select col1,col2…from tab_old;

(2)建一个新表,架构跟旧表完全一样,但没有内容:

create tabletab_new as select * from tab_old where 1=2;

删除表:

drop table tabname;--删除表结构

只删除表数据:

delete from tabname;(dml操作需要事务提交)

truncate table tabname;(ddl操作立即生效)

修改表:

修改表名:alter table skate_test rename to table_name

添加表注释:comment on table scott. table_name is '注释内容';

添加列注释:comment column on table_name.column_name is '注释内容';

 

添加、修改、删除列

添加:alter table tablename add (column datatype [defaultvalue][null/not null],….);

修改:alter table tablename modify(column datatype [default value][null/not null],….);

删除:alter table tablename drop(column);

 

注:列增加后将不能删除。db2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。

oracle cascade用法

cascade 关键字主要用于级联,级联删除、级联更新等,综合评价:★★

删除用户:drop user user_name; 删除用户,drop user user_name cascade; 删除此用户名下的所有表和视图

alter table table_name add constraint fk_tn_dept foreign key(dept) references dept(deptno) ([on delete set null],[on delete cascade]);

添加、删除约束(主键、外键)

1、创建表的同时创建主键约束

(1)无命名

create table student (

studentid int primary key not null,

studentname varchar(8),

ageint);

(2)有命名

create table students (

studentid int ,

studentname varchar(8),

age int,

constraint yy primary key(pk_studentid));

 

2、向表中添加主键约束

alter table student add constraint pk_studentprimary key(pk_studentid);

 

3、删除表中已有的主键约束

(1)有命名

alter table students drop constraint yy;

(2)无命名

可用SELECT * FROM user_cons_columns WHEREtable_name = ’ student’

查找表中主键名称得student表中的主键名为SYS_C002715

alter table student drop constraintSYS_C002715;

 

主键与外键

主键是表格里的(一个或多个)字段,只用来定义表格里的行;主键里的值总是唯一的。

外键是一个用来建立两个表格之间关系的约束。这种关系一般都涉及一个表格里的主键字段与另外一个表格(尽管可能是同一个表格)里的一系列相连的字段。那么这些相连的字段就是外键。

 

创建、删除索引

创建:create [unique] index idx_name on tabname(col_name….)

删除:drop index idxname

注:索引是不可更改的,想更改必须删除重新建。

索引作用:

第一、通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

第二、可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

第三、可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

第四、在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

第五, 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

 

索引的作用?和它的优点缺点是什么?

索引就一种特殊的查询表,数据库的搜索引擎可以利用它加速对数据的检索。

优点:它很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。索引可以是唯一的,创建索引允许指定单个列或者是多个列。

缺点:是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。

 

创建、修改、删除视图

创建、修改:create or replace view view_name as select statement

删除:drop view view_name

二、 SQL查询

基本的SQL语句

选择:select * from table1 where 范围

插入:insert into table1(field1,field2) values(value1,value2)

删除:delete from table1 where 范围

更新:update table1 set field1=value1 where 范围

查找:select * from table1 where field1 like ’%value1%’ ---like语法★★★

排序:select * from table1 order by field1,field2 [desc]

总数:select count(*) as totalcount from table1

求和:select sum(field1) as sumvalue from table1

平均:select avg(field1) as avgvalue from table1

最大:select max(field1) as maxvalue from table1

最小:select min(field1) as minvalue from table1

union、minus、intersect

a:union 运算符

union 运算符通过组合其他两个结果表(例如table1 和 table2)并消去表中任何重复行而派生出一个结果表。当 all 随 union 一起使用时(即 union all),不消除重复行。两种情况下,派生表的每一行不是来自 table1 就是来自 table2。

b:minus 运算符

minus 运算符通过包括所有在 table1 中但不在 table2 中的行并消除所有重复行而派生出一个结果表。

c:intersect 运算符

intersect 运算符通过只包括 table1 和 table2 中都有的行并消除所有重复行而派生出一个结果表。

内连接、外连接

内连接,只连接匹配的行

selecta.c1,b.c2 from a join b on a.c3 = b.c3;

左外连接

包含左边表的全部行以及右边表中全部匹配的行

selecta.c1,b.c2 from a left join b on a.c3 = b.c3;

右外连接

包含右边表的全部行以及左边表中全部匹配的行

selecta.c1,b.c2 from a right join b on a.c3 = b.c3;

全外连接

综合评价:★★

包含左、右两个表的全部行

selecta.c1,b.c2 from a full join b on a.c3 = b.c3;

非等连接

使用等值以外的条件来匹配左、右两个表中的行

selecta.c1,b.c2 from a join b on a.c3 != b.c3;

自连接

使用同一张表中的不同字段进行匹配

select *from t1 a,t1 b where a.a1 = b.a3

交叉连接

综合评价:★★

生成笛卡尔积——它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行一一匹

 select a.c1,b.c2 from a,b;

多表关联

select * from a left inner join b ona.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .....

 

内联接,外联接区别:内连接是保证两个表中所有的行都要满足连接条件,而外连接则不然。

在外连接中,某些不满条件的列也会显示出来,也就是说,只限制其中一个表的行,而不限制另一个表的行。分左连接、右连接、全连接三种

 

oracle8i,9i 表连接方法。

一般的相等连接: select * from a, b where a.id = b.id; 这个就属于内连接。

对于外连接:

oracle中可以使用“(+) ”来表示,9i可以使用left/right/fullouter join

leftouter join:左外关联

selecte.last_name, e.department_id, d.department_name

fromemployees e

leftouter join departments d

on(e.department_id = d.department_id);

等价于

selecte.last_name, e.department_id, d.department_name

fromemployees e, departments d

wheree.department_id=d.department_id(+)

结果为:所有员工及对应部门的记录,包括没有对应部门编号department_id的员工记录。

rightouter join:右外关联

selecte.last_name, e.department_id, d.department_name

fromemployees e

rightouter join departments d

on(e.department_id = d.department_id);

等价于

selecte.last_name, e.department_id, d.department_name

fromemployees e, departments d

wheree.department_id(+)=d.department_id

 

结果为:所有员工及对应部门的记录,包括没有任何员工的部门记录。

fullouter join:全外关联

selecte.last_name, e.department_id, d.department_name

fromemployees e

fullouter join departments d

on(e.department_id = d.department_id);

结果为:所有员工及对应部门的记录,包括没有对应部门编号department_id的员工记录和没有任何员工的部门记录。

oracle8i是不能在左右两个表上同时加上(+),转换成一个左联接一个右连接

全外连接语法

综合评价: ★★

selectt1.id,t2.id from table1 t1,table t2 where t1.id=t2.id(+)

union

selectt1.id,t2.id from table1 t1,table t2 where t1.id(+)=t2.id

子查询、关联子查询

综合评价:★★★

关联子查询是一种包含子查询的特殊类型的查询。查询里包含的子查询会真正请求外部查询的值,从而形成一个类似于循环的状况。

 

子查询:

注:表名1:a 表名2:b

select a,b,c from a where a in (select dfrom b) 或者: select a,b,c froma where a in (1,2,3)

 

关联子查询:

显示文章、提交人和最后回复时间

select a.title,a.username,b.adddate fromtable a, (selectmax(adddate) adddate from table where table.title=a.title) b where b.adddate = a.adddate;——需要显示B表中的字段

 

scott模式中,找出每个部门中最高工资的人

select a.deptno, a.* from emp a

where a.sal = (select max(b.sal)from emp b where b.deptno = a.deptno) ;——不需要显示B表中的字段

between、in、exists

综合评价:★★

between 使用方法

between的用法,between限制查询数据范围时包括了边界值,not between不包括

select * from table1 where time between time1 and time2

select a,b,c, from table1 where a not between 数值1 and 数值2

in 的使用方法

select * from table1 where a [not] in (‘值1’,’值2’,’值4’,’值6’)

exists 的使用方法

注:存在两张表,table1 table2

select * from table1 t1 where [not] exists(select * from table2 t2 where t1.id = t2.id)

in、exists使用区别:如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in

复制表(insert into … select、select … into … from)

综合评价:★★

insert into select语句

语句形式为:insert into table2(field1,field2,...) select value1,value2,... from table1

注意:

(1)要求目标表table2必须存在,并且字段field,field2...也必须存在

(2)注意table2的主键约束,如果table2有主键而且不为空,则 field1, field2...中必须包括主键

 

select into from语句

语句形式为:select vale1, value2 into table2 from table1;

对比:create table tab_new as select col1,col2…from tab_old;

要求目标表table2不存在,因为在插入时会自动创建表table2,并将table1中指定字段数据复制到table2中

三、 SQL查询优化

SQL 优化的实质:在保证结果正确的前提下,充份利用索引,减少表扫描的 I/O 次数,尽量少访问数据块,尽量避免全表扫描和其他额外开销。

oracle 常用的两种优化器:RBO(rule-based-optimizer)和CBO(cost-based-optimizer)。 目前更多地采用CBO(cost-based-optimizer)基于开销的优化器。在 CBO 方式下,Oracle 会根据表及索引的状态信息来选择计划;在 RBO 方 式下 ,Oracle 会根据自己内部设置的一些 规则来决定选择计划。

1、尽量少用 IN 操作符

基本上所有的 IN 操作符都可以用EXISTS 代替。IN 和EXISTS 操作的选择要根据主子表数据量大小来具体考虑。

 

2、尽量用 NOT EXISTS 或者外连接替代 NOT IN 操作符

因为 NOT IN 不能应用表的索引

 

3、尽量不用“<>”或者“!=”操作符

综合评价:★★

不等于操作符是通过全表扫描处理,大于或者小于会使用标的索引。如:a<>0改为 a>0 or a<0

 

4、在设计表时,把索引列设置为 NOT NULL

判断字段否为空是不会应用索引,因为 B 树索引不会索引空值的。

 

5、尽量不用通配符“%”或者“_”作为查询字符串的第一个字符

当通配符“%”或者“_”作为查询字符串的第一个字符时,索引不会被使用 。比如用 T 表中 Column1LIKE ‘%5400%’ 这个条件会产生全表扫描,如果改成 Column1 LIKE ’X5400%’ OR Column1 LIKE ’B5400%’ 则会利用 Column1 的索引进行两个范围的查询。

 

6、Where 子句中避免在索引列上使用计算

如果索引不是基于函数的,那么当在 Where 子句中对索引列使用函数时索引不再起作用。比如:substr(no,1,4)=’5400’,优化处理:no like ‘5400%’

trunc(hiredate)=trunc(sysdate) ,优化处理:hiredate >=trunc(sysdate) and hiredate<trunc(sysdate+1)

 

7、用“>=”替代“>”

综合评价:★★

大于或小于操作符一般情况下是不用调整的,因为它有索引就会采用索引查找,但有的情况下可以对它进行优化,如一个表有 100 万记录,一个数值型字段 A, 30 万记 录的 A=0,30 万记录的 A=1,39 万记录的 A=2,1 万记录的 A=3。那么执行 A>2 与 A>=3 的效果就有很大的区别了,因为 A>2时 ORACLE 会先找出为 2 的记录索引再进行比较,而 A>=3 时 ORACLE 则直接找到=3 的记录索引

 

8、利用 SGA 共享池,避开 parse 阶段

同一功能同一性能不同写法 SQL的影响。 如一个 SQL 在

A 程序员写的为:

Select * from zl_yhjbqk;

B 程序员写的为:

Select * from dlyx.zl_yhjbqk(带表所有者的前缀);

C 程序员写的为:

Select * from DLYX.ZLYHJBQK(大写表名);

D 程序员写的为:

Select * from DLYX.ZLYHJBQK(中间多了空格)。

 

以上四个 SQL 在 ORACLE 分析整理之后产生的结果及执行的时间是一样的,但是从ORACLE 共享内存 SGA 的原理,可以得出 ORACLE 对每个 SQL 都会对其进行一次分析,并且占用共享内存,如果将 SQL 的字符串及格式写得完全相同则 ORACLE 只会分析一次,共享内存也只会留下一次的分析结果,这不仅可以减少分析 SQL 的时间,而且可以减少共享内存重复的信息,ORACLE 也可以准确统计 SQL 的执行频率。

不同区域出现的相同的 SQL 语句要保证查询字符完全相同,建议经常使用变量来代替常量,以尽量使用重复 SQL 代码,以利用 SGA 共享池,避开 parse 阶段,防止相同的 SQL 语句被多次分析,提高执行速度。因此使用存储过程,是一种很有效的提高 share pool 共享率,跳过 parse 阶段,提高效率的办法。

 

9、WHERE 后面的条件顺序要求

综合评价:★★

WHERE 后面的条件,表连接语句写在最前,可以过滤掉最大数量记录的条件最后。ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。

10、使用表的别名,并将之作为每列的前缀

当在 SQL 语句中连接多个表时,使用表的别名,并将之作为每列的前缀。这样可以减少解析时间

11、进行了显式或隐式的运算的字段不能进行索引

综合评价:★★

比如:

ss_df+20>50,优化处理:ss_df>30

‘X’||hbs_bh>’X5400021452’,优化处理:hbs_bh>’5400021542’

sk_rq+5=sysdate,优化处理:sk_rq=sysdate-5

hbs_bh=5401002554,优化处理:hbs_bh=’ 5401002554’,注:此条件对hbs_bh 进行隐式的 to_number 转换,因为 hbs_bh 字段是字符型。

 

12、用 UNION ALL 代替 UNION

UNION 是最常用的集操作,使多个记录集联结成为单个集,对返回的数据行有唯一性要求, 所以 oracle 就需要进行 SORTUNIQUE 操作(与使用 distinct 时操作类似),如果结果集又比较大,则操作会比较慢;

UNION ALL 操作不排除重复记录行,所以会快很多,如果数据本身重复行存在可能性较小时,用 union all 会比用 union 效率高很多!

 

13、其他操作

综合评价:★★

尽量使用 packages: Packages 在第一次调用时能将整个包 load 进内存,对提高性能有帮助。s

尽量使用 cached sequences来生成 primary key :提高主键生成速度和使用性能。很好地利用空间:如用 VARCHAR2 数据类型代替 CHAR 等

使用 SQL 优化工具:SQLexpert;toad;explain-table;PL/SQL;OEM

 

14、通过改变 oracle 的 SGA 的大小

综合评价:★★★

SGA:数据库的系统全局区。

SGA 主要由三部分构成:共享池、数据缓冲区、日志缓冲区

1、 共享池又由两部分构成:共享 SQL 区和数据字典缓冲区。共享SQL 区专门存放用户 SQL 命令,oracle 使用最近最少使用等优先级算法来更新覆盖;数据字典缓冲区(library cache)存放数据库运行的动态信息。数据库运行一段时间后, DBA 需要查看这些内存区域的命中率以从数据库角度对数据库性能调优。通过执行下述语句查看:

 

select (sum(pins - reloads)) / sum(pins)"Lib Cache" from v$librarycache;

--查看共享 SQL 区的重用率,最好在 90%以上,否则需要增加共享池的大小。 select (sum(gets - getmisses - usage - fixED)) / sum(gets) "Row Cache" fromv$rowcache;

--查看数据字典缓冲区的命中率,最好在 90%以上,否则需要增加共享池的大小。

2、 数据缓冲区:存放 SQL 运行结果抓取到的 datablock;

SELECT name, value FROM v$sysstat WHERE name IN ('db block gets',

'consistent gets','physical reads');

--查看数据库数据缓冲区的使用情况。查询出来的结果可以计算出来数据缓冲区 的使用命中率=1 - (physical reads / (db block gets + consistent gets) )。命中率应该 在 90%以上,否则需要增加数据缓冲区的大小。

3、 日志缓冲区:存放数据库运行生成的日志。

select name,value from v$sysstat where name in ('redo entries','redo log space requests');

--查看日志缓冲区的使用情况。查询出的结果可以计算出日志缓冲区的申请失败

率:申请失败率=requests/entries,申请失败率应该接近于 0,否则说明日志缓冲区开设太小,需要增加 ORACLE 数据库的日志缓冲区。

SQL调整最关注的是什么

检查系统的i/o问题

sar-d能检查整个系统的iostat(iostatistics)

查看该SQL的response time(db block gets/consistentgets/physical reads/sorts (disk))

四、 ORACLE SQL性能优化

1. 选用适合的ORACLE优化器

ORACLE的优化器共有3种:a. RULE (基于规则) b. COST(基于成本) c. CHOOSE (选择性)

 

设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS. 你当然也在SQL句级或是会话(session)级对其进行覆盖.

为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运行analyze 命令,以增加数据库中的对象统计信息(object statistics)的准确性.

如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行过analyze命令有关. 如果table已经被analyze过, 优化器模式将自动成为CBO , 反之,数据库将采用RULE形式的优化器.

在缺省情况下,ORACLE采用CHOOSE优化器, 为了避免那些不必要的全表扫描(full table scan) , 你必须尽量避免使用CHOOSE优化器,而直接采用基于规则或者基于成本的优化器.

2. 访问Table的方式

ORACLE 采用两种访问表中记录的方式:

a. 全表扫描

全表扫描就是顺序地访问表中每条记录.ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描.

b. 通过ROWID访问表

你可以采用基于ROWID的访问方式情况,提高访问表的效率, ,ROWID包含了表中记录的物理位置信息..ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系. 通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高.

3. 共享SQL语句

为了不重复解析相同的SQL语句,在第一次解析之后,ORACLE将SQL语句存放在内存中.这块位于系统全局区域SGA(system global area)的共享池(shared bufferpool)中的内存可以被所有的数据库用户共享. 因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同,ORACLE就能很快获得已经被解析的语句以及最好的执行路径. ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用.可惜的是ORACLE只对简单的表提供高速缓冲(cache buffering) ,这个功能并不适用于多表连接查询.数据库管理员必须在init.ora中为这个区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大了.

当你向ORACLE 提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句.这里需要注明的是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须完全相同(包括空格,换行等).

共享的语句必须满足三个条件:

A. 字符级的比较:当前被执行的语句和共享池中的语句必须完全相同.

B. 两个语句所指的对象必须完全相同:

C. 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)

4. 选择最有效率的表名顺序(只在基于规则的优化器中有效)

ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理. 在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.当ORACLE处理多个表时, 会运用排序及合并的方式连接它们.首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并.

例如:

表 TAB1 16,384 条记录

表 TAB2 1 条记录

 

选择TAB2作为基础表 (最好的方法)

select count(*) from tab1,tab2 执行时间0.96秒

 

选择TAB2作为基础表 (不佳的方法)

select count(*) from tab2,tab1 执行时间26.09秒

 

如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表.

例如:

EMP表描述了LOCATION表和CATEGORY表的交集.

SELECT *

FROM LOCATION L ,

CATEGORY C,

EMP E

WHERE E.EMP_NO BETWEEN 1000 AND 2000

AND E.CAT_NO = C.CAT_NO

AND E.LOCN = L.LOCN

将比下列SQL更有效率

SELECT *

FROM EMP E ,

LOCATION L ,

CATEGORY C

WHERE E.CAT_NO = C.CAT_NO

AND E.LOCN = L.LOCN

AND E.EMP_NO BETWEEN 1000 AND 2000

 

5. 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’;

 

6. SELECT子句中避免使用 ‘ * ‘

当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ‘*’ 是一个方便的方法.不幸的是,这是一个非常低效的方法. 实际上,ORACLE在解析的过程中, 会将’*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间.

 

7. 减少访问数据库的次数

当执行每条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;

注意:

在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200

ORACLE SQL性能优化系列 (三)

8. 使用DECODE函数来减少处理时间

使用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子句中.

 

9. 整合简单,无关联的数据库访问

如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)

例如:

SELECT NAME

FROM EMP

WHERE EMP_NO = 1234;

 

SELECT NAME

FROM DPT

WHERE DPT_NO = 10 ;

 

SELECT NAME

FROM CAT

WHERE CAT_TYPE = ‘RD’;

 

上面的3个查询可以被合并成一个:

 

SELECT E.NAME , D.NAME , C.NAME

FROM CAT C , DPT D , EMP E,DUAL X

WHERE NVL(‘X’,X.DUMMY) = NVL(‘X’,E.ROWID(+))

AND NVL(‘X’,X.DUMMY) = NVL(‘X’,D.ROWID(+))

AND NVL(‘X’,X.DUMMY) = NVL(‘X’,C.ROWID(+))

AND E.EMP_NO(+) = 1234

AND D.DEPT_NO(+) = 10

AND C.CAT_TYPE(+) = ‘RD’;

 

(译者按: 虽然采取这种方法,效率得到提高,但是程序的可读性大大降低,所以读者还是要权衡之间的利弊)

 

10. 删除重复记录

最高效的删除重复记录方法 ( 因为使用了ROWID)

DELETE FROM EMP E

WHERE E.ROWID > (SELECT MIN(X.ROWID)

FROM EMP X

WHERE X.EMP_NO = E.EMP_NO);

 

12. 尽量多使用COMMIT

 

只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:

COMMIT所释放的资源:

a. 回滚段上用于恢复数据的信息.

b. 被程序语句获得的锁

c. redo log buffer 中的空间

d. ORACLE为管理上述3种资源中的内部花费

(译者按: 在使用COMMIT时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌不可得兼)

ORACLE SQL性能优化系列 (四)

13. 计算记录条数

和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的. 例如 COUNT(EMPNO)

(译者按: 在CSDN论坛中,曾经对此有过相当热烈的讨论, 作者的观点并不十分准确,通过实际的测试,上述三种方法并没有显著的性能差别)

 

14. 用Where子句替换HAVING子句

避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作.如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销.

例如:

低效:

SELECT REGION,AVG(LOG_SIZE)

FROM LOCATION

GROUP BY REGION

HAVING REGION REGION != ‘SYDNEY’

AND REGION != ‘PERTH’

 

高效

SELECT REGION,AVG(LOG_SIZE)

FROM LOCATION

WHERE REGION REGION != ‘SYDNEY’

AND REGION != ‘PERTH’

GROUP BY REGION

(译者按:HAVING 中的条件一般用于对一些集合函数的比较,如COUNT() 等等. 除此而外,一般的条件应该写在WHERE子句中)

 

15. 减少对表的查询

在含有子查询的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) FROMEMP_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;

 

 

16. 通过内部函数提高SQL效率.

 

SELECTH.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC,COUNT(*)

FROM HISTORY_TYPE T,EMP E,EMP_HISTORY H

WHERE H.EMPNO = E.EMPNO

AND H.HIST_TYPE = T.HIST_TYPE

GROUP BYH.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC;

 

通过调用下面的函数可以提高效率.

FUNCTION LOOKUP_HIST_TYPE(TYP INNUMBER) RETURN VARCHAR2

AS

TDESC VARCHAR2(30);

CURSOR C1 IS

SELECT TYPE_DESC

FROM HISTORY_TYPE

WHERE HIST_TYPE = TYP;

BEGIN

OPEN C1;

FETCH C1 INTO TDESC;

CLOSE C1;

RETURN (NVL(TDESC,’?’));

END;

 

FUNCTION LOOKUP_EMP(EMP IN NUMBER)RETURN VARCHAR2

AS

ENAME VARCHAR2(30);

CURSOR C1 IS

SELECT ENAME

FROM EMP

WHERE EMPNO=EMP;

BEGIN

OPEN C1;

FETCH C1 INTO ENAME;

CLOSE C1;

RETURN (NVL(ENAME,’?’));

END;

 

SELECT H.EMPNO,LOOKUP_EMP(H.EMPNO),

H.HIST_TYPE,LOOKUP_HIST_TYPE(H.HIST_TYPE),COUNT(*)

FROM EMP_HISTORY H

GROUP BY H.EMPNO , H.HIST_TYPE;

 

ORACLE SQL性能优化系列 (六)

20. 用表连接替换EXISTS

通常来说 , 采用表连接的方式比EXISTS更有效率

SELECT ENAME

FROM EMP E

WHERE EXISTS (SELECT ‘X’

FROM DEPT

WHERE DEPT_NO = E.DEPT_NO

AND DEPT_CAT = ‘A’);

 

(更高效)

SELECT ENAME

FROM DEPT D,EMP E

WHERE E.DEPT_NO = D.DEPT_NO

AND DEPT_CAT = ‘A’ ;

 

21. 用EXISTS替换DISTINCT

当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换

 

例如:

低效:

SELECT DISTINCT DEPT_NO,DEPT_NAME

FROM DEPT D,EMP E

WHERE D.DEPT_NO = E.DEPT_NO

高效:

SELECT DEPT_NO,DEPT_NAME

FROM DEPT D

WHERE EXISTS ( SELECT ‘X’

FROM EMP E

WHERE E.DEPT_NO = D.DEPT_NO);

 

EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果.

 

22. 识别’低效执行’的SQL语句

 

用下列SQL工具找出低效SQL:

 

SELECT EXECUTIONS , DISK_READS,BUFFER_GETS,

ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2)Hit_radio,

ROUND(DISK_READS/EXECUTIONS,2)Reads_per_run,

SQL_TEXT

FROM V$SQLAREA

WHERE EXECUTIONS>0

AND BUFFER_GETS > 0

AND(BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8

ORDER BY 4 DESC;

 

(译者按: 虽然目前各种关于SQL优化的图形化工具层出不穷,但是写出自己的SQL工具来解决问题始终是一个最好的方法)

 

23. 使用TKPROF 工具来查询SQL性能状态

 

SQL trace 工具收集正在执行的SQL的性能状态数据并记录到一个跟踪文件中. 这个跟踪文件提供了许多有用的信息,例如解析次数.执行次数,CPU使用时间等.这些数据将可以用来优化你的系统.

 

设置SQL TRACE在会话级别: 有效

 

ALTER SESSION SET SQL_TRACE TRUE

 

设置SQL TRACE 在整个数据库有效仿, 你必须将SQL_TRACE参数在init.ora中设为TRUE, USER_DUMP_DEST参数说明了生成跟踪文件的目录

ORACLE SQL性能优化系列 (七 )

24. 用EXPLAIN PLAN 分析SQL语句

EXPLAIN PLAN 是一个很好的分析SQL语句的工具,它甚至可以在不执行SQL的情况下分析语句. 通过分析,我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称.

你需要按照从里到外,从上到下的次序解读分析的结果. EXPLAIN PLAN分析的结果是用缩进的格式排列的, 最内部的操作将被最先解读, 如果两个操作处于同一层中,带有最小操作号的将被首先执行.

NESTED LOOP是少数不按照上述规则处理的操作, 正确的执行路径是检查对NESTED LOOP提供数据的操作,其中操作号最小的将被最先处理.

 

译者按:

 

通过实践, 感到还是用SQLPLUS中的SET TRACE 功能比较方便.

举例:

 

SQL> list

1 SELECT *

2 FROM dept, emp

3* WHERE emp.deptno = dept.deptno

SQL> set autotrace on exp;/*traceonly 可以不显示执行结果*/

或者SQL> set autotrace traceonly exp;

SQL> /

14 rows selected.

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 NESTED LOOPS

2 1 TABLE ACCESS (FULL) OF 'EMP'

3 1 TABLE ACCESS (BY INDEX ROWID) OF'DEPT'

4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPT'(UNIQUE)

 

Statistics

----------------------------------------------------------

0 recursive calls

2 db block gets

30 consistent gets

0 physical reads

0 redo size

2598 bytes sent via SQL*Net to client

503 bytes received via SQL*Net fromclient

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

14 rows processed

 

通过以上分析,可以得出实际的执行步骤是:

1. TABLE ACCESS (FULL) OF 'EMP'

2. INDEX (UNIQUE SCAN) OF 'PK_DEPT'(UNIQUE)

3. TABLE ACCESS (BY INDEX ROWID) OF'DEPT'

4. NESTED LOOPS (JOINING 1 AND 3)

 

ORACLE SQL性能优化系列 (八)

25. 用索引提高效率

索引是表的一个概念部分,用来提高检索数据的效率. 实际上,ORACLE使用了一个复杂的自平衡B-tree结构. 通常,通过索引查询数据比全表扫描要快. 当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引. 同样在联结多个表时使用索引也可以提高效率. 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证.

除了那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列. 通常, 在大型表中使用索引特别有效. 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率.

虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间来

存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢.

译者按:

定期的重构索引是有必要的.

ALTER INDEX <INDEXNAME> REBUILD<TABLESPACENAME>

 

26. 索引的操作

ORACLE对索引有两种访问模式.

索引唯一扫描 ( INDEX UNIQUE SCAN)

大多数情况下, 优化器通过WHERE子句访问INDEX.

例如:

表LODGING有两个索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER.

 

 

 

SELECT *

FROM LODGING

WHERE LODGING = ‘ROSE HILL’;

在内部 , 上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式执行下一步检索.

如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访问表). 因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果.

下面SQL只需要INDEXUNIQUE SCAN 操作.

 

SELECT LODGING

FROM LODGING

WHERE LODGING = ‘ROSE HILL’;

 

索引范围查询(INDEX RANGE SCAN)

适用于两种情况:

1. 基于一个范围的检索

2. 基于非唯一性索引的检索

 

例1:

 

SELECT LODGING

FROM LODGING

WHERE LODGING LIKE ‘M%’;

 

WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK . 由于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描低一些.

 

例2:

SELECT LODGING

FROM LODGING

WHERE MANAGER = ‘BILL GATES’;

 

这个SQL的执行分两步,LODGING$MANAGER的索引范围查询(得到所有符合条件记录的ROWID) 和下一步同过ROWID访问表得到LODGING列的值. 由于LODGING$MANAGER是一个非唯一性的索引,数据库不能对它执行索引唯一扫描.

 

由于SQL返回LODGING列,而它并不存在于LODGING$MANAGER索引中, 所以在索引范围查询后会执行一个通过ROWID访问表的操作.

 

WHERE子句中, 如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始, 索引将不被采用.

 

SELECT LODGING

FROM LODGING

WHERE MANAGER LIKE ‘%HANMAN’;

在这种情况下,ORACLE将使用全表扫描.

ORACLE SQL性能优化系列 (九)

27. 基础表的选择

基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问). 根据优化器的不同, SQL语句中基础表的选择是不一样的.

如果你使用的是CBO (COST BASED OPTIMIZER),优化器会检查SQL语句中的每个表的物理大小,索引的状态,然后选用花费最低的执行路径.

如果你用RBO (RULE BASED OPTIMIZER) , 并且所有的连接条件都有索引对应, 在这种情况下, 基础表就是FROM 子句中列在最后的那个表.

举例:

SELECT A.NAME , B.MANAGER

FROM WORKERA,

LODGING B

WHERE A.LODGING = B.LODING;

由于LODGING表的LODING列上有一个索引, 而且WORKER表中没有相比较的索引, WORKER表将被作为查询中的基础表.

 

28. 多个平等的索引

当SQL语句的执行路径可以使用分布在多个表上的多个索引时, ORACLE会同时使用多个索引并在运行时对它们的记录进行合并, 检索出仅对全部索引有效的记录.

在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引. 然而这个规则只有

当WHERE子句中索引列和常量比较才有效.如果索引列和其他表的索引类相比较. 这种子句在优化器中的等级是非常低的.

如果不同表中两个想同等级的索引将被引用, FROM子句中表的顺序将决定哪个会被率先使用. FROM子句中最后的表的索引将有最高的优先级.

如果相同表中两个想同等级的索引将被引用, WHERE子句中最先被引用的索引将有最高的优先级.

举例:

DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引.

SELECT ENAME,

FROM EMP

WHERE DEPT_NO = 20

AND EMP_CAT = ‘A’;

这里,DEPTNO索引将被最先检索,然后同EMP_CAT索引检索出的记录进行合并. 执行路径如下:

 

TABLE ACCESS BY ROWID ON EMP

AND-EQUAL

INDEX RANGE SCAN ON DEPT_IDX

INDEX RANGE SCAN ON CAT_IDX

 

29. 等式比较和范围比较

当WHERE子句中有索引列,ORACLE不能合并它们,ORACLE将用范围比较.

 

举例:

DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引.

SELECT ENAME

FROM EMP

WHERE DEPTNO > 20

AND EMP_CAT = ‘A’;

 

这里只有EMP_CAT索引被用到,然后所有的记录将逐条与DEPTNO条件进行比较. 执行路径如下:

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON CAT_IDX

 

30. 不明确的索引等级

 

当ORACLE无法判断索引的等级高低差别,优化器将只使用一个索引,它就是在WHERE子句中被列在最前面的.

举例:

DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引.

 

SELECT ENAME

FROM EMP

WHERE DEPTNO > 20

AND EMP_CAT > ‘A’;

 

这里, ORACLE只用到了DEPT_NO索引. 执行路径如下:

 

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON DEPT_IDX

 

译者按:

我们来试一下以下这种情况:

SQL> select index_name, uniquenessfrom user_indexes where table_name = 'EMP';

 

INDEX_NAME UNIQUENES

---------------------------------------

EMPNO UNIQUE

EMPTYPE NONUNIQUE

 

SQL> select * from emp where empno>= 2 and emp_type = 'A' ;

 

no rows selected

 

 

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (BY INDEX ROWID) OF'EMP'

2 1 INDEX (RANGE SCAN) OF 'EMPTYPE'(NON-UNIQUE)

 

虽然EMPNO是唯一性索引,但是由于它所做的是范围比较, 等级要比非唯一性索引的等式比较低!

ORACLE SQL性能优化系列 (十)

31. 强制索引失效

如果两个或以上索引具有相同的等级,你可以强制命令ORACLE优化器使用其中的一个(通过它,检索出的记录数量少) .

举例:

SELECT ENAME

FROM EMP

WHERE EMPNO = 7935

AND DEPTNO + 0 = 10 /*DEPTNO上的索引将失效*/

AND EMP_TYPE || ‘’ = ‘A’ /*EMP_TYPE上的索引将失效*/

这是一种相当直接的提高查询效率的办法. 但是你必须谨慎考虑这种策略,一般来说,只有在你希望单独优化几个SQL时才能采用它.

 

这里有一个例子关于何时采用这种策略,

假设在EMP表的EMP_TYPE列上有一个非唯一性的索引而EMP_CLASS上没有索引.

SELECT ENAME

FROM EMP

WHERE EMP_TYPE = ‘A’

AND EMP_CLASS = ‘X’;

 

优化器会注意到EMP_TYPE上的索引并使用它. 这是目前唯一的选择. 如果,一段时间以后, 另一个非唯一性建立在EMP_CLASS上,优化器必须对两个索引进行选择,在通常情况下,优化器将使用两个索引并在他们的结果集合上执行排序及合并. 然而,如果其中一个索引(EMP_TYPE)接近于唯一性而另一个索引(EMP_CLASS)上有几千个重复的值. 排序及合并就会成为一种不必要的负担. 在这种情况下,你希望使优化器屏蔽掉EMP_CLASS索引.

用下面的方案就可以解决问题.

SELECT ENAME

FROM EMP

WHERE EMP_TYPE = ‘A’

AND EMP_CLASS||’’ = ‘X’;

 

32. 避免在索引列上使用计算.

WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描.

 

举例:

 

低效:

SELECT …

FROM DEPT

WHERE SAL * 12 > 25000;

 

高效:

SELECT …

FROM DEPT

WHERE SAL > 25000/12;

 

译者按:

这是一个非常实用的规则,请务必牢记

 

33. 自动选择索引

如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性.

在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引.

 

举例:

SELECT ENAME

FROM EMP

WHERE EMPNO = 2326

AND DEPTNO = 20 ;

 

这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录.

TABLE ACCESS BY ROWID ON EMP

INDEX UNIQUE SCAN ON EMP_NO_IDX

 

34. 避免在索引列上使用NOT

通常, 我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的

影响. 当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描.

举例:

 

低效: (这里,不使用索引)

 

SELECT …

FROM DEPT

WHERE DEPT_CODE NOT = 0;

 

高效: (这里,使用了索引)

 

SELECT …

FROM DEPT

WHERE DEPT_CODE > 0;

 

需要注意的是,在某些时候,ORACLE优化器会自动将NOT转化成相对应的关系操作符.

NOT > to <=

NOT >= to <

NOT < to >=

NOT <= to >

 

 

译者按:

在这个例子中,作者犯了一些错误. 例子中的低效率SQL是不能被执行的.

我做了一些测试:

 

SQL> select * from emp where NOTempno > 1;

no rows selected

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (BY INDEX ROWID) OF'EMP'

2 1 INDEX (RANGE SCAN) OF 'EMPNO'(UNIQUE)

 

SQL> select * from emp where empno<= 1;

no rows selected

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (BY INDEX ROWID) OF'EMP'

2 1 INDEX (RANGE SCAN) OF 'EMPNO'(UNIQUE)

 

两者的效率完全一样,也许这符合作者关于” 在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符” 的观点.

 

35. 用>=替代>

 

如果DEPTNO上有一个索引,

 

高效:

 

SELECT *

FROM EMP

WHERE DEPTNO >=4

 

低效:

 

SELECT *

FROM EMP

WHERE DEPTNO >3

 

两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录.

ORACLE SQL性能优化系列 (十一)

36. 用UNION替换OR (适用于索引列)

通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低.

在下面的例子中, LOC_ID 和REGION上都建有索引.

高效:

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE LOC_ID = 10

UNION

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE REGION = “MELBOURNE”

 

低效:

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE LOC_ID = 10 OR REGION = “MELBOURNE”

 

如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面.

 

注意:

 

WHERE KEY1 = 10 (返回最少记录)

OR KEY2 = 20 (返回最多记录)

 

ORACLE 内部将以上转换为

WHERE KEY1 = 10 AND

((NOT KEY1 = 10) AND KEY2 = 20)

 

译者按:

 

下面的测试数据仅供参考: (a = 1003 返回一条记录 , b = 1 返回1003条记录)

SQL> select * from unionvsor /*1sttest*/

2 where a = 1003 or b = 1;

1003 rows selected.

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 CONCATENATION

2 1 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

3 2 INDEX (RANGE SCAN) OF 'UB'(NON-UNIQUE)

4 1 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

5 4 INDEX (RANGE SCAN) OF 'UA'(NON-UNIQUE)

Statistics

----------------------------------------------------------

0 recursive calls

0 db block gets

144 consistent gets

0 physical reads

0 redo size

63749 bytes sent via SQL*Net to client

7751 bytes received via SQL*Net fromclient

68 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1003 rows processed

SQL> select * from unionvsor /*2ndtest*/

2 where b = 1 or a = 1003 ;

1003 rows selected.

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 CONCATENATION

2 1 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

3 2 INDEX (RANGE SCAN) OF 'UA'(NON-UNIQUE)

4 1 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

5 4 INDEX (RANGE SCAN) OF 'UB'(NON-UNIQUE)

Statistics

----------------------------------------------------------

0 recursive calls

0 db block gets

143 consistent gets

0 physical reads

0 redo size

63749 bytes sent via SQL*Net to client

7751 bytes received via SQL*Net fromclient

68 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1003 rows processed

 

SQL> select * from unionvsor /*3rdtest*/

2 where a = 1003

3 union

4 select * from unionvsor

5 where b = 1;

1003 rows selected.

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 SORT (UNIQUE)

2 1 UNION-ALL

3 2 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

4 3 INDEX (RANGE SCAN) OF 'UA'(NON-UNIQUE)

5 2 TABLE ACCESS (BY INDEX ROWID) OF'UNIONVSOR'

6 5 INDEX (RANGE SCAN) OF 'UB'(NON-UNIQUE)

Statistics

----------------------------------------------------------

0 recursive calls

0 db block gets

10 consistent gets

0 physical reads

0 redo size

63735 bytes sent via SQL*Net to client

7751 bytes received via SQL*Net fromclient

68 SQL*Net roundtrips to/from client

1 sorts (memory)

0 sorts (disk)

1003 rows processed

用UNION的效果可以从consistentgets和 SQL*NET的数据交换量的减少看出

 

37. 用IN来替换OR

 

下面的查询可以被更有效率的语句替换:

 

低效:

 

SELECT….

FROM LOCATION

WHERE LOC_ID = 10

OR LOC_ID = 20

OR LOC_ID = 30

 

高效

SELECT…

FROM LOCATION

WHERE LOC_IN IN (10,20,30);

 

译者按:

这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的. 

 

38. 避免在索引列上使用IS NULL和IS NOT NULL

避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引.对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中.

举例:

如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入). 然而如果

所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空. 因此你可以插入1000

条具有相同键值的记录,当然它们都是空!

 

因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.

举例:

 

低效: (索引失效)

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE IS NOT NULL;

 

高效: (索引有效)

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE >=0;

ORACLE SQL性能优化系列 (十二)

39. 总是使用索引的第一个列

如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引.

译者按:

这也是一条简单而重要的规则. 见以下实例.

 

SQL> create table multiindexusage (inda number , indb number , descr varchar2(10));

Table created.

SQL> create index multindex on multiindexusage(inda,indb);

Index created.

SQL> set autotrace traceonly

 

SQL> select * from multiindexusagewhere inda = 1;

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (BY INDEX ROWID) OF'MULTIINDEXUSAGE'

2 1 INDEX (RANGE SCAN) OF 'MULTINDEX'(NON-UNIQUE)

 

SQL> select * from multiindexusagewhere indb = 1;

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (FULL) OF'MULTIINDEXUSAGE'

 

很明显, 当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引

 

 

40. ORACLE内部操作

当执行查询时,ORACLE采用了内部的操作. 下表显示了几种重要的内部操作.

ORACLE Clause
内部操作

ORDER BY
SORT ORDER BY

UNION
UNION-ALL

MINUS
MINUS

INTERSECT
INTERSECT

DISTINCT,MINUS,INTERSECT,UNION
SORT UNIQUE

MIN,MAX,COUNT
SORT AGGREGATE

GROUP BY
SORT GROUP BY

ROWNUM
COUNT or COUNT STOPKEY

Queries involving Joins
SORT JOIN,MERGE JOIN,NESTED LOOPS

CONNECT BY
CONNECT BY

 

 


41. 用UNION-ALL 替换UNION ( 如果有可能的话)

 

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序.

如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高.

 

举例:

低效:

    SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = ’31-DEC-95’

UNION

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = ’31-DEC-95’

高效:

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = ’31-DEC-95’

UNION ALL

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = ’31-DEC-95’

 

译者按:

需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是

要从业务需求分析使用UNION ALL的可行性.

UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这

块内存的优化也是相当重要的. 下面的SQL可以用来查询排序的消耗量

 

Select substr(name,1,25) "SortArea Name",

substr(value,1,15) "Value"

from v$sysstat

where name like 'sort%'

 

42. 使用提示(Hints)

对于表的访问,可以使用两种Hints.

FULL 和 ROWID

 

FULL hint 告诉ORACLE使用全表扫描的方式访问指定表.

例如:

SELECT /*+ FULL(EMP) */ *

FROM EMP

WHERE EMPNO = 7893;

 

ROWID hint 告诉ORACLE使用TABLE ACCESS BY ROWID的操作访问表.

 

通常, 你需要采用TABLEACCESS BY ROWID的方式特别是当访问大表的时候, 使用这种方式, 你需要知道ROIWD的值或者使用索引.

如果一个大表没有被设定为缓存(CACHED)表而你希望它的数据在查询结束是仍然停留

在SGA中,你就可以使用CACHE hint 来告诉优化器把数据保留在SGA中. 通常CACHE hint 和 FULL hint 一起使用.

例如:

SELECT /*+ FULL(WORKER) CACHE(WORKER)*/*

FROM WORK;

 

索引hint 告诉ORACLE使用基于索引的扫描方式. 你不必说明具体的索引名称

例如:

SELECT /*+ INDEX(LODGING) */ LODGING

FROM LODGING

WHERE MANAGER = ‘BILL GATES’;

 

在不使用hint的情况下, 以上的查询应该也会使用索引,然而,如果该索引的重复值过多而你的优化器是CBO, 优化器就可能忽略索引. 在这种情况下, 你可以用INDEX hint强制ORACLE使用该索引.

 

ORACLE hints 还包括ALL_ROWS, FIRST_ROWS, RULE,USE_NL, USE_MERGE, USE_HASH 等等.

 

译者按:

使用hint , 表示我们对ORACLE优化器缺省的执行路径不满意,需要手工修改.

这是一个很有技巧性的工作. 我建议只针对特定的,少数的SQL进行hint的优化.

对ORACLE的优化器还是要有信心(特别是CBO)

ORACLE SQL性能优化系列 (十三)

43. 用WHERE替代ORDER BY

ORDER BY 子句只在两种严格的条件下使用索引.

 

ORDER BY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序.

ORDER BY中所有的列必须定义为非空.

 

WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.

 

例如:

表DEPT包含以下列:

 

DEPT_CODE PK NOT NULL

DEPT_DESC NOT NULL

DEPT_TYPE NULL

 

非唯一性的索引(DEPT_TYPE)

 

低效: (索引不被使用)

SELECT DEPT_CODE

FROM DEPT

ORDER BY DEPT_TYPE

 

EXPLAIN PLAN:

SORT ORDER BY

TABLE ACCESS FULL

 

高效: (使用索引)

 

SELECT DEPT_CODE

FROM DEPT

WHERE DEPT_TYPE > 0

 

EXPLAIN PLAN:

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON DEPT_IDX

译者按:

ORDER BY 也能使用索引! 这的确是个容易被忽视的知识点. 我们来验证一下:

SQL> select * from emp order byempno;

Execution Plan

----------------------------------------------------------

0 SELECT STATEMENT Optimizer=CHOOSE

1 0 TABLE ACCESS (BY INDEX ROWID) OF'EMP'

2 1 INDEX (FULL SCAN) OF 'EMPNO'(UNIQUE)

 

44. 避免改变索引列的类型.

当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换.

 

假设 EMPNO是一个数值类型的索引列.

 

SELECT …

FROM EMP

WHERE EMPNO = ‘123’

 

实际上,经过ORACLE类型转换, 语句转化为:

SELECT …

FROM EMP

WHERE EMPNO = TO_NUMBER(‘123’)

 

幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变.

 

现在,假设EMP_TYPE是一个字符类型的索引列.

SELECT …

FROM EMP

WHERE EMP_TYPE = 123

 

这个语句被ORACLE转换为:

SELECT …

FROM EMP

WHERE TO_NUMBER(EMP_TYPE)=123

 

因为内部发生的类型转换, 这个索引将不会被用到!

译者按:

为了避免ORACLE对你的SQL进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE会优先转换数值类型到字符类型.

 

45. 需要当心的WHERE子句

某些SELECT 语句中的WHERE子句不使用索引. 这里有一些例子.

在下面的例子里, ‘!=’ 将不使用索引. 记住, 索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中.

不使用索引:

SELECT ACCOUNT_NAME

FROM TRANSACTION

WHERE AMOUNT !=0;

使用索引:

SELECT ACCOUNT_NAME

FROM TRANSACTION

WHERE AMOUNT >0;

 

下面的例子中, ‘||’是字符连接函数. 就象其他函数那样, 停用了索引.

不使用索引:

SELECT ACCOUNT_NAME,AMOUNT

FROM TRANSACTION

WHERE ACCOUNT_NAME||ACCOUNT_TYPE=’AMEXA’;

使用索引:

SELECT ACCOUNT_NAME,AMOUNT

FROM TRANSACTION

WHERE ACCOUNT_NAME = ‘AMEX’

AND ACCOUNT_TYPE=’ A’;

 

下面的例子中, ‘+’是数学函数. 就象其他数学函数那样, 停用了索引.

不使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE AMOUNT + 3000 >5000;

使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE AMOUNT > 2000 ;

下面的例子中,相同的索引列不能互相比较,这将会启用全表扫描.

不使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE ACCOUNT_NAME =NVL(:ACC_NAME,ACCOUNT_NAME);

使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE ACCOUNT_NAME LIKE NVL(:ACC_NAME,’%’);

 

译者按:

如果一定要对使用函数的列启用索引, ORACLE新的功能: 基于函数的索引(Function-Based Index) 也许是一个较好的方案.

CREATE INDEX EMP_I ON EMP(UPPER(ename)); /*建立基于函数的索引*/

SELECT * FROM emp WHERE UPPER(ename) = ‘BLACKSNAIL’; /*将使用索引*/

 

ORACLE SQL性能优化系列 (十四) 完结篇

46. 连接多个扫描

如果你对一个列和一组有限的值进行比较, 优化器可能执行多次扫描并对结果进行合并连接.

举例:

SELECT *

FROM LODGING

WHERE MANAGER IN (‘BILL GATES’,’KENMULLER’);

 

优化器可能将它转换成以下形式

SELECT *

FROM LODGING

WHERE MANAGER = ‘BILL GATES’

OR MANAGER = ’KEN MULLER’;

 

当选择执行路径时, 优化器可能对每个条件采用LODGING$MANAGER上的索引范围扫描. 返回的ROWID用来访问LODGING表的记录 (通过TABLE ACCESS BY ROWID 的方式). 最后两组记录以连接(CONCATENATION)的形式被组合成一个单一的集合.

 

Explain Plan :

 

SELECT STATEMENT Optimizer=CHOOSE

CONCATENATION

TABLE ACCESS (BY INDEX ROWID) OFLODGING

INDEX (RANGE SCAN ) OF LODGING$MANAGER(NON-UNIQUE)

TABLE ACCESS (BY INDEX ROWID) OFLODGING

INDEX (RANGE SCAN ) OF LODGING$MANAGER(NON-UNIQUE)

译者按:

本节和第37节似乎有矛盾之处.

 

 

47. CBO下使用更具选择性的索引

基于成本的优化器(CBO, Cost-Based Optimizer)对索引的选择性进行判断来决定索引的使用是否能提高效率.

如果索引有很高的选择性, 那就是说对于每个不重复的索引键值,只对应数量很少的记录.

比如, 表中共有100条记录而其中有80个不重复的索引键值. 这个索引的选择性就是80/100 = 0.8 . 选择性越高,通过索引键值检索出的记录就越少.

如果索引的选择性很低, 检索数据就需要大量的索引范围查询操作和ROWID 访问表的

操作. 也许会比全表扫描的效率更低.

 

译者按:

下列经验请参阅:

a. 如果检索数据量超过30%的表中记录数.使用索引将没有显著的效率提高.

b. 在特定情况下, 使用索引也许会比全表扫描慢, 但这是同一个数量级上的

区别. 而通常情况下,使用索引比全表扫描要块几倍乃至几千倍!

 

 

48. 避免使用耗费资源的操作

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎

执行耗费资源的排序(SORT)功能.DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序.

例如,一个UNION查询,其中每个查询都带有GROUP BY子句,GROUP BY会触发嵌入排序(NESTED SORT) ; 这样, 每个查询需要执行一次排序, 然后在执行UNION时, 又一个唯一排序(SORTUNIQUE)操作被执行而且它只能在前面的嵌入排序结束后才能开始执行. 嵌入的排序的深度会大大影响查询的效率.

通常, 带有UNION,MINUS , INTERSECT的SQL语句都可以用其他方式重写.

译者按:

如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强

 

 

49. 优化GROUP BY

提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.

 

低效:

SELECT JOB , AVG(SAL)

FROM EMP

GROUP JOB

HAVING JOB = ‘PRESIDENT’

OR JOB = ‘MANAGER’

高效:

SELECT JOB , AVG(SAL)

FROM EMP

WHERE JOB = ‘PRESIDENT’

OR JOB = ‘MANAGER’

GROUP JOB

 

译者按:

本节和14节相同. 可略过.

 

50. 使用日期

当使用日期是,需要注意如果有超过5位小数加到日期上, 这个日期会进到下一天!

 

例如:

1.

SELECT TO_DATE(‘01-JAN-93’+.99999)

FROM DUAL;

 

Returns:

’01-JAN-93 23:59:59’

2.

SELECT TO_DATE(‘01-JAN-93’+.999999)

FROM DUAL;

 

Returns:

’02-JAN-93 00:00:00’

 

译者按:

虽然本节和SQL性能优化没有关系, 但是作者的功力可见一斑

 

51. 使用显式的游标(CURSORs)

使用隐式的游标,将会执行两次操作. 第一次检索记录, 第二次检查TOO MANY ROWS 这个exception . 而显式游标不执行第二次操作.

 

52. 优化EXPORT和IMPORT

使用较大的BUFFER(比如10MB, 10,240,000)可以提高EXPORT和IMPORT的速度.

ORACLE将尽可能地获取你所指定的内存大小,即使在内存不满足,也不会报错.这个值至少要和表中最大的列相当,否则列值会被截断.

译者按:

可以肯定的是, 增加BUFFER会大大提高EXPORT , IMPORT的效率. (曾经碰到过一个CASE, 增加BUFFER后,IMPORT/EXPORT快了10倍!)

作者可能犯了一个错误: “这个值至少要和表中最大的列相当,否则列值会被截断. “

其中最大的列也许是指最大的记录大小.

关于EXPORT/IMPORT的优化,CSDN论坛中有一些总结性的贴子,比如关于BUFFER参数,COMMIT参数等等, 详情请查.

 

53. 分离表和索引

总是将你的表和索引建立在不同的表空间内(TABLESPACES). 决不要将不属于ORACLE内部系统的对象存放到SYSTEM表空间里. 同时,确保数据表空间和索引表空间置于不同的硬盘上.

 

译者按:

“同时,确保数据表空间和索引表空间置与不同的硬盘上.”可能改为如下更为准确 “同时,确保数据表空间和索引表空间置与不同的硬盘控制卡控制的硬盘上.”

 

五、 SQL编程

数据库表、列

列出数据库里所有的表名

select table_name from user_tables; --当前用户的表

select table_name from all_tables; --所有用户的表

select table_name from dba_tables; --包括系统表

列出表里的所有的列

desc table_name

表备份

综合评价:★★

下面的例子会制作 "persons" 表的备份复件:

select * into persons_backup from persons

in 子句可用于向另一个数据库中拷贝表:

select * into persons in 'backup.mdb' from persons

事务

事务:作为一个逻辑单元执行的一系列操作,一个逻辑工作单元必须有四个属性,称为 acid(原子性、一致性、隔离性和持久性)属性。

原子性:事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。

一致性:事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 b 树索引或双向链表)都必须是正确的。

隔离性:由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。

持久性:事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。

 

锁:共享锁、互斥锁

共享锁:如果事务t对数据a加上共享锁后,则其他事务只能对a再加共享锁,不能加排他锁,直到已释放所有共享锁。获准共享锁的事务只能读数据,不能修改数据。

排他锁:如果事务t对数据a加上排他锁后,则其他事务不能再对a加任任何类型的锁,直到在事务的末尾将资源上的锁释放为止。获准排他锁的事务既能读数据,又能修改数据。

两段锁协议:阶段1:加锁阶段阶段2:解锁阶段

触发器

触发器: 当满足触发器条件,则系统自动执行触发器的触发体。

触发时间:有before,after.触发事件:有insert,update,delete三种。触发类型:有行触发、语句触发

触发器的作用:触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化。可以联级运算。如,某表上的触发器上包含对另一个表的数据操作,而该操作又会导致该表触发器被触发。

事前触发器运行于触发事件发生之前,而事后触发器运行于触发事件发生之后。通常事前触发器可以获取事件之前和新的字段值。

语句级触发器可以在语句执行前或后执行,而行级触发在触发器所影响的每一行触发一次。

 

视图、游标

综合评价:★★★

视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,视图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。

 

游标:一个游标(cursor)可以被看作指向结果集(a set of rows)中一行的指针(pointer)。游标每个时间点只能指向一行,但是可以根据需要指向结果集中其他的行。

例如:SELECT * FROM employees WHERE sex='M'会返回所有性别为男的雇员,在初始的时候,游标被放置在结果集中第一行的前面。使游标指向第一行,要执行FETCH。当游标指向结果集中一行的时候,可以对这行数据进行加工处理,要想得到下一行数据,要继续执行FETCH。FETCH操作可以重复执行,直到完成结果集中的所有行

 

在存储过程中使用游标:

声明游标、打开游标、根据需要一次一行,讲游标指向的数据取到本地变量(local variables)中、结束时关闭游标。

 

显式游标:当查询返回结果超过一行时,就需要一个显式游标,此时用户不能使用select into语句。PL/SQL管理隐式游标,当查询开始时隐式游标打开,查询结束时隐式游标自动关闭。显式游标在PL/SQL块的声明部分声明,在执行部分 或异常处理部分打开,取出数据,关闭。

 

使用游标:我们所说的游标通常是指 显式游标

CURSOR cursor_name IS select_statement;

在PL/SQL中游标名是一个未声明变量,不能给游标名赋值或用于表达式中。例:

DELCARE

CURSOR C_EMP IS SELECT empno,ename,salary

FROM emp

WHERE salary>2000

ORDER BY ename;

........

BEGIN

打开关闭游标:使用游标中的值之前应该首先打开游标初始化查询处理。

OPEN cursor_name; CLOSE cursor_name

从游标提取数据:从游标得到一行数据使用FETCH命令。每一次提取数据后,游标都指向结果集的下一行。语法如下:FETCHcursor_name INTO variable [, variable...]

对于SELECT定义的游标的每一列,FETCH变量列表都应该有一个变量与之相对应,变量的类型也要相同。返回结果集不止一条就要使用循环。例:

 

SET SERVERIUTPUT ON
DECLARE

v_ename EMP.ENAME%TYPE;

v_salary EMP.SALARY%TYPE;

CURSOR c_emp IS SELECT ename,salary FROM emp;

BEGIN

OPEN c_emp;

LOOP

FETCH c_emp INTO v_ename,v_salary;

EXIT WHEN c_emp%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(/'Salary of Employee/'|| v_ename ||/'is/'|| v_salary);

END LOOP;

CLOSE c_emp;

END;

记录变量:定义一个记录变量使用TYPE命令和%ROWTYPE。

记录变量用于从游标中提取数据行,当游标选择很多列的时候,那么使用记录比为每列声明一个变量要方便得多。

 

关 键 词:当在表上使用%ROWTYPE并将从游标中取出的值放入记录中时,如果要选择表中所有列,那么在SELECT子句中使用*比将所有列名列出来要得多。例:

SET SERVERIUTPUT ON

DECLARE

R_emp EMP%ROWTYPE;

CURSOR c_emp IS SELECT * FROM emp;

BEGIN

OPEN c_emp;

LOOP

FETCH c_emp INTO r_emp;

EXIT WHEN c_emp%NOTFOUND;

DBMS_OUT.PUT.PUT_LINE(/'Salary of Employee/'||r_emp.ename||/'is/'|| r_emp.salary);

END LOOP;

CLOSE c_emp;

END;

%ROWTYPE也可以用游标名来定义,这样的话就必须要首先声明游标:

 

SET SERVERIUTPUT ON

DECLARE

CURSOR c_emp IS SELECT ename,salary FROM emp;

R_emp c_emp%ROWTYPE;

BEGIN

OPEN c_emp;

LOOP

FETCH c_emp INTO r_emp;

EXIT WHEN c_emp%NOTFOUND;

DBMS_OUT.PUT.PUT_LINE(/'Salary of Employee/'

||r_emp.ename||/'is/'|| r_emp.salary);

END LOOP;

CLOSE c_emp;

END;

带参数的游标:与存储过程和函数相似,可以将参数传递给游标并在查询中使用。语法如下:

CURSOR cursor_name[(parameter[,parameter],...)] IS select_statement;

定义参数的:

Parameter_name [IN] data_type[{:=|DEFAULT} value]

游标只能接受传递的值,不能返回值。参数只定义数据类型,没有大小。另外可以给参数设定一个缺省值,当没有参数值传递给游标时,就使用缺省值。

打开游标时给参数赋值:

OPEN cursor_name [value [, value]....];

参数值可以是文字或变量。例:

DECALRE

CURSOR c_dept IS SELECT * FROM dept ORDER BY deptno;

CURSOR c_emp (p_dept VARACHAR2) IS

SELECT ename,salary

FROM emp

WHERE deptno=p_dept

ORDER BY ename

r_dept DEPT%ROWTYPE;

v_ename EMP.ENAME%TYPE;

v_salary EMP.SALARY%TYPE;

v_tot_salary EMP.SALARY%TYPE;

BEGIN

OPEN c_dept;

LOOP

FETCH c_dept INTO r_dept;

EXIT WHEN c_dept%NOTFOUND;

DBMS_OUTPUT.PUT_LINE

(/'Department:/'|| r_dept.deptno||/'-/'||r_dept.dname);

v_tot_salary:=0;

OPEN c_emp(r_dept.deptno);

LOOP

FETCH c_emp INTO v_ename,v_salary;

EXIT WHEN c_emp%NOTFOUND;

DBMS_OUTPUT.PUT_LINE

(/'Name:/'|| v_ename||/' salary:/'||v_salary);

v_tot_salary:=v_tot_salary+v_salary;

END LOOP;

CLOSE c_emp;

DBMS_OUTPUT.PUT_LINE

(/'Toltal Salary for dept:/'|| v_tot_salary);

END LOOP;

CLOSE c_dept;

END;

游标FOR循环

在大多数时候我们在设计程序的时候都遵循下面的步骤:

1、打开游标。

2、开始循环。

3、从游标中取值。

4、那一行被返回。

5、处理。

6、关闭循环。

7、关闭游标。

可以简单的把这一类代码称为游标用于循环。但还有一种循环与这种类型不相同,这就是FOR循环,用于FOR循环的游标按照正常的声明方式声明,它的优点在于不需要显式的打开、关闭、取数据,测试数据的存在、定义存放数据的变量等等。游标FOR循环的语法如下:

FOR record_name IN

(corsor_name[(parameter[,parameter]...)]

| (query_difinition)

LOOP

statements

END LOOP;

用for循环重写上面的例子:

DECALRE

CURSOR c_dept IS SELECT deptno,dname FROM dept ORDER BY deptno;

CURSOR c_emp (p_dept VARACHAR2) IS

SELECT ename,salary

FROM emp

WHERE deptno=p_dept

ORDER BY ename

v_tot_salary EMP.SALARY%TYPE;

BEGIN

FOR r_dept IN c_dept LOOP

DBMS_OUTPUT.PUT_LINE

(/'Department:/'|| r_dept.deptno||/'-/'||r_dept.dname);

v_tot_salary:=0;

FOR r_emp IN c_emp(r_dept.deptno) LOOP

DBMS_OUTPUT.PUT_LINE

(/'Name:/' || v_ename || /'salary:/' || v_salary);

v_tot_salary:=v_tot_salary+v_salary;

END LOOP;

DBMS_OUTPUT.PUT_LINE

(/'Toltal Salary for dept:/'|| v_tot_salary);

END LOOP;

END;

在游标FOR循环中使用查询

在游标FOR循环中可以定义查询,由于没有显式声明所以游标没有名字,记录名通过游标查询来定义。

DECALRE

v_tot_salary EMP.SALARY%TYPE;

BEGIN

FOR r_dept IN (SELECT deptno,dname FROM dept ORDER BY deptno) LOOP

DBMS_OUTPUT.PUT_LINE(/'Department:/'|| r_dept.deptno||/'-/'||r_dept.dname);

v_tot_salary:=0;

FOR r_emp IN (SELECT ename,salary

   FROM emp

   WHERE deptno=p_dept

   ORDER BY ename) LOOP

DBMS_OUTPUT.PUT_LINE(/'Name:/'|| v_ename||/' salary:/'||v_salary);

v_tot_salary:=v_tot_salary+v_salary;

END LOOP;

DBMS_OUTPUT.PUT_LINE(/'Toltal Salary for dept:/'|| v_tot_salary);

END LOOP;

END;

游标中的子查询

CURSOR C1 IS SELECT * FROM emp

WHERE deptno NOT IN (SELECT deptno

FROM dept

WHERE dname!=/'ACCOUNTING/');

游标中的更新和删除(没学)

在PL/SQL中依然可以使用UPDATE和DELETE语句更新或删除数据行。显式游标只有在需要获得多行数据的情况下使用。PL/SQL提供了仅仅使用游标就可以执行删除或更新记录的方法。

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

语法:

 

FOR UPDATE [OF [schema.]table.column[,[schema.]table.column]..
 

[nowait]

在多表查询中,使用OF子句来锁定特定的表,如果忽略了OF子句,那么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会话锁定,那么正常情况下ORACLE将等待,直到数据行解锁。

在UPDATE和DELETE中使用WHERECURRENT 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

存储过程

综合评价:★★★★

存储过程基础知识

存储过程:一组为了完成特定功能的SQL语句集,经编译后存储在数据库中。

存储过程的创建需要CREATE PROCEDURE 系统权限,如果需要被其他用户Schema使用需要CREATE ANY PROCEDURE 权限。存储过程的执行需要 EXECUTE权限或者 EXECUTE ANY PROCEDURE 权限。

单独赋予权限:grant execute on MY_PROCEDURE to Jelly;

调用存储过程: executeMY_PROCEDURE( 'ONE PARAMETER');

 

存储过程(PROCEDURE)和函数(FUNCTION)的区别:

A: 函数有限制只能返回一个标量,而存储过程可以返回多个;

B: 函数可以嵌入到SQL语句中执行. 而存储过程不行。存储过程执行必须调用EXECUTE

包(PACKAGE)是function,procedure,variables 和SQL 语句的组合。package允许多个procedure使用同一个变量和游标。

 

存储过程的特点:

1.存储过程运行的速度比较快。

2. 可保证数据的安全性和完整性。

3.可以降低网络的通信量。

4:存储过程可以接受参数、输出参数、返回单个或多个结果集以及返回值。

5:存储过程可以包含程序流、逻辑以及对数据库的查询。

 

存储过程创建语法

CREATE [ OR REPLACE ] PROCEDURE [ schema.]procedure

[(argument [IN | OUT | IN OUT ] [NO COPY] datatype

[, argument [IN | OUT | IN OUT ] [NO COPY] datatype]...

)]

[ authid { current_user | definer }]

{ is | as } { pl/sql_subprogram_body |

language { java name 'String' | c [ name, name] library lib_name

}]

Sql 代码:

CREATE PROCEDURE sam.credit (acc_no IN NUMBER, amount IN NUMBER) AS

BEGIN

UPDATE accounts

SET balance = balance + amount

WHERE account_id = acc_no;

END;

IN, OUT, IN OUT用来修饰参数。

IN 表示这个变量必须被调用者赋值然后传入到PROCEDURE进行处理。

OUT 表示PRCEDURE 通过这个变量将值传回给调用者。

IN OUT 则是这两种的组合。

authid代表两种权限:

定义者权限(difiner right 默认),执行者权限(invoker right)。

定义者权限说明这个procedure中涉及的表、视图等对象所需要的权限只要定义者拥有权限的话就可以访问。

执行者权限则需要调用这个 procedure的用户拥有相关表和对象的权限。

Oracle存储过程的语法

1. 基本结构

CREATE OR REPLACE PROCEDURE 存储过程名字
(
参数1 IN NUMBER,
参数2 IN NUMBER
) AS
变量1 INTEGER :=0;
变量2 DATE;
BEGIN

END 存储过程名字

2. SELECT INTO STATEMENT

将select查询的结果存入到变量中,可以同时将多个列存储多个变量中,必须有一条记录,否则抛出异常(如果没有记录抛出NO_DATA_FOUND)

例子:

BEGIN
SELECT col1,col2 into 变量1,变量2 FROM typestruct where xxx;
EXCEPTION
WHEN NO_DATA_FOUND THEN
xxxx;

WHEN OTHERS THEN

xxxx;
END;
...

3. IF 判断

IF V_TEST=1 THEN
BEGIN
do something
END;
END IF;

4. while 循环

WHILE V_TEST=1 LOOP
BEGIN
XXXX
END;
END LOOP;

5. 变量赋值

V_TEST := 123;

6. 用for in 使用cursor

...
IS
CURSOR cur IS SELECT * FROM xxx;
BEGIN
FOR cur_result in cur LOOP
BEGIN
V_SUM :=cur_result.列名1+cur_result.列名2
END;
END LOOP;
END;

7. 带参数的cursor

CURSOR C_USER(C_ID NUMBER) IS SELECT NAME FROM USER WHERE TYPEID=C_ID;
OPEN C_USER(变量值);
LOOP
FETCH C_USER INTO V_NAME;
EXIT FETCH C_USER%NOTFOUND;
do something
END LOOP;
CLOSE C_USER;

8. 用pl/sql developer debug

连接数据库后建立一个Test WINDOW
在窗口输入调用SP的代码,F9开始debug,CTRL+N单步调试

 

9. Pl/Sql中执行存储过程

在sql*plus中:

declare
--必要的变量声明,视你的过程而定
begin
execute yourprocudure(parameter1,parameter2,...);
end
/

在SQL/PLUS中调用存储过程,显示结果:

SQL>set serveoutput on --打开输出

SQL>var info1 number; --输出1

SQL>var info2 number; --输出2

SQL>declare

var1 varchar2(20); --输入1

var2 varchar2(20); --输入2

var3 varchar2(20); --输入2

BEGIN

pro(var1,var2,var3,:info1,:info2);

END;

/

SQL>print info1;

SQL>print info2;

注:在EXECUTE IMMEDIATE STR语句是SQLPLUS中动态执行语句,它在执行中会自动提交,类似于DP中FORMS_DDL语句,在此语句中STR是不能换行的,只能通过连接字符"||",或者在换行时加上"-"连接字符。

 

绑定变量

绑定变量是指在SQL语句中使用变量,改变变量的值来改变SQL语句的执行结果。

优点:使用绑定变量,可以减少SQL语句的解析,能减少数据库引擎消耗在SQL语句解析上的资源。提高了编程效率和可靠性。减少访问数据库的次数, 就能实际上减少oracle的工作量。

缺点:经常需要使用动态SQL的写法,由于参数的不同,可能SQL的执行效率不同;绑定变量是相对文本变量来讲的,所谓文本变量是指在SQL直接书写查询条件,这样的SQL在不同条件下需要反复解析,绑定变量是指使用变量来代替直接书写条件,查询bind value在运行时传递,然后绑定执行。优点是减少硬解析,降低cpu的争用,节省shared_pool缺点是不能使用histogram,SQL优化比较困难

 

索引

综合评价:★★★

select *from user_indexes 查询现有的索引

select *from user_ind_columns 可获知索引建立在那些字段上

1、 什么是索引?

一种用于提升查询效率的数据库对象;通过快速定位数据的方法,减少磁盘I/O操作;索引信息与表独立存放;Oracle数据库自动使用和维护索引。

 

2、 索引分类?

唯一索引和非唯一索引

 

3、 创建索引的两种方式?

自动创建,在定义主键或唯一键约束是系统会自动在相应的字段上创建唯一性索引。

手动创建,用户可以在其他列上创建非唯一索引,加速查询。

 

4、 索引优缺点

索引的优点

1.大大加快数据的检索速度;

2.创建唯一性索引,保证数据库表中每一行数据的唯一性;

3.加速表和表之间的连接;

4.在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。

 

有索引且查询条件能使用索引时,数据库会先选取索引,根据索引内容和查询条件,查询出rowid,再根据rowid取出需要的数据。由于索引内容通常比全表内容要少很多,因此通过先读索引,能减少i/o,提高查询性能。

索引的缺点

1.索引需要占物理空间。

2.当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度。

 

5、 创建索引的原则

创建索引:创建索引一般有以下两个目的:维护被索引列的唯一性和提供快速访问表中数据

的策略。

--在select 操作占大部分的表上创建索引;

--在where 子句中出现最频繁的列上创建索引;

--在选择性高的列上创建索引(补充索引选择性,最高是1,eg:primary key)

--复合索引的主列应该是最有选择性的和where 限定条件最常用的列,并以此类推第二列……。

--小于5M 的表,最好不要使用索引来查询,表越小,越适合用全表扫描。

 

6、 使用索引的原则

--查询结果是所有数据行的5%以下时,使用index 查询效果最好;

 

--where 条件中经常用到表的多列时,使用复合索引效果会好于几个单列索引。因为当sql

语句所查询的列,全部都出现在复合索引中时,此时由于Oracle 只需要查询索引块即可获

得所有数据,当然比使用多个单列索引要快得多;

 

--索引利于select,但对经常insert,delte尤其update 的表,会降低效率。

eg:试比较下面两条SQL 语句(emp 表的deptno 列上建有ununique index):

语句A:SELECT dname, deptno FROM dept WHEREdeptno NOT IN

(SELECTdeptno FROM emp);

语句B:SELECT dname, deptno FROM dept WHERE NOTEXISTS

(SELECTdeptno FROM emp WHERE dept.deptno = emp.deptno);

注意:这两条查询语句实现的结果是相同的,但是执行语句A 的时候,ORACLE 会对整个emp 表进行扫描,没有使用建立在emp 表上的deptno 索引,执行语句B 的时候,由于在子查询中使用了联合查询,ORACLE 只是对emp 表进行的部分数据扫描,并利用了deptno 列的索引,

所以语句B 的效率要比语句A 的效率高。

 

----where子句中的这个字段,必须是复合索引的第一个字段;

eg:一个索引是按f1, f2, f3 的次序建立的,若where 子句是f2 = : var2, 则因为f2 不是索引的第1 个字段,无法使用该索引。

 

---- where 子句中的这个字段,不应该参与任何形式的计算:任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

 

---- 应尽量熟悉各种操作符对Oracle 是否使用索引的影响:以下这些操作会显式( explicitly )地阻止Oracle 使用索引: is null ; is not null; not in; !=; like ;

numeric_col+0;date_col+0;char_col||' '; to_char; to_number,to_date 等。

Eg:select jobid from mytabs where isReq='0' and to_date (updatedate)>= to_Date ( '2001-7-18','YYYY-MM-DD');--updatedate 列的索引也不会生效。

 

7、 创建索引

createindex abc on student(sid,sname);

createindex abc1 on student(sname,sid);

这两种索引方式是不一样的,索引abc 对Select * from student where sid=1; 这样的查询语句更有效索引abc1 对Select * from student where sname=’louis’; 这样的查询语句更有效因此建立索引的时候,字段的组合顺序是非常重要的。一般情况下,需要经常访问的字段放在组合字段的前面

 

8、 索引的存储

索引和表都是独立存在的。在为索引指定表空间的时候,不要将被索引的表和索引指向同

一个表空间,这样可以避免产生IO 冲突。使Oracle 能够并行访问存放在不同硬盘中的索引数据和表数据,更好的提高查询速度。

 

9、 删除索引

dropindex PK_DEPT1;

 

10、 索引类型

索引有b-tree、bit、cluster等类型。oracle使用了一个复杂的自平衡b-tree结构;

B 树索引(B-Tree Index)

创建索引的默认类型,结构是一颗树,采用的是平衡B 树算法:

右子树节点的键值大于等于父节点的键值;左子树节点的键值小于等于父节点的键值

比如有数据:100,101,102,103,104,105,106

 

 

位图索引(BitMap Index)

如果表中的某些字段取值范围比较小,比如职员性别、分数列ABC 级等。只有两个值。

这样的字段如果建B 树索引没有意义,不能提高检索速度。这时我们推荐用位图索引

CreateBitMap Index student on(sex);

 

11、 管理索引

1)先插入数据后创建索引

2)设置合理的索引列顺序

3)限制每个表索引的数量

4)删除不必要的索引

5)为每个索引指定表空间

6)经常做insert,delete 尤其是update的表最好定期exp/imp 表数据,整理数据,降低碎片(缺点:要停应用,以保持数据一致性,不实用);有索引的最好定期rebuild 索引(rebuild期间只允许表的select 操作,可在数据库较空闲时间提交),以降低索引碎片,提高效率

六、 oracle数据库

exp、imp备份数据库

综合评价:★★★

1)命令行备份,如:

a)将数据库test完全导出,用户名system 密码manager 导出到d:\daochu.dmp中,

exp system/manager@test file=d:\daochu.dmp full=y

b)将数据库中system用户与sys用户的表导出 ,

expsystem/manager@test file=d:\daochu.dmp owner=(system,sys)

c)将数据库中的表inner_notify、notify_staff_relat导出

expaichannel/aichannel@testdb2 file= d:\data\newsmgnt.dmp

tables=(inner_notify,notify_staff_relat)

详细内容请查阅oracle相关资料

2)dmp文件导入

将d:\daochu.dmp中的数据导入 test数据库中,

imp system/manager@test file=d:\daochu.dmp,如果已经存在需要导入的表,则使用如下命令imp system/manager@test file=d:\daochu.dmp ignore=y

将d:\daochu.dmp中的表table1 导入

imp system/manager@test file=d:\daochu.dmp tables=(table1)

 

不借助第三方工具,怎样查看SQL的执行计划

i) 使用explain plan,查询plan_table;

explainplan

setstatement_id='query1'

for

select *

from a

whereaa=1;

selectoperation, options, object_name, object_type, id, parent_id

fromplan_table

wherestatement_id = 'query1'

order byid;

 

ii)SQLplus中的set trace 即可看到execution plan statistics

setautotrace on;

 

如何使用cbo,cbo与rule的区别

if 初始化参数 optimizer_mode = choose then --(8i default)

if 做过表分析

then 优化器 optimizer=cbo(cost); /*高效*/

else

优化器 optimizer=rbo(rule); /*高效*/

end if;

end if;

 

区别:

rule根据规则选择最佳执行路径来运行查询。

cbo根据表统计找到最低成本的访问数据的方法确定执行计划。

使用cbo需要注意:

i) 需要经常对表进行analyze命令进行分析统计;

ii) 需要稳定执行计划;

iii)需要使用提示(hint);

 

使用rule需要注意:

i) 选择最有效率的表名顺序

ii) 优化SQL的写法;

在optimizer_mode=choose时,如果表有统计信息(分区表外),优化器将选择cbo,否则选rbo。

rbo遵循简单的分级方法学,使用15种级别要点,当接收到查询,优化器将评估使用到的要点数目,然后选择最佳级别(最

少的数量)的执行路径来运行查询。

cbo尝试找到最低成本的访问数据的方法,为了最大的吞吐量或最快的初始响应时间,计算使用不同的执行计划的成本,并

选择成本最低的一个,关于表的数据内容的统计被用于确定执行计划。

 

如何定位重要(消耗资源多)的SQL

使用cpu多的用户session

selecta.sid, spid, status, substr (a.program, 1, 40) prog, a.terminal,a.SQL_text,osuser, value / 60 /

100value

fromv$session a, v$process b, v$sesstat c

wherec.statistic# = 12 and c.sid = a.sid and a.paddr = b.addr

order byvalue desc;

select SQL_textfrom v$SQL

wheredisk_reads &gt; 1000 or (executions &gt; 0 and buffer_gets/executions&gt; 30000);

 

如何跟踪某个session的SQL

利用trace 跟踪

altersession set SQLtrace on;

column SQLformat a200;

selectmachine, SQL_text SQL

from v$SQLtexta, v$session b

whereaddress = SQL_address

andmachine = '&a'

order byhash_value, piece;

execdbms_system.set_SQL_trace_in_session(sid,serial#,&amp;SQL_trace);

selectsid,serial# from v$session where sid = (select sid from v$mystat where rownum =1);

execdbms_system.set_ev(&amp;sid,&amp;serial#,&amp;event_10046,&amp;level_12,'');

 

如何稳定(固定)执行计划

可以在SQL语句中指定执行计划。使用hints;

query_rewrite_enabled= true

star_transformation_enabled= true

optimizer_features_enable= 9.2.0

创建并使用stored outline

 

pctused and pctfree 表示什么含义有什么作用

pctused与pctfree控制数据块是否出现在freelist中, pctfree控制数据块中保留用于update的空间,当数据块中的

freespace小于pctfree设置的空间时,该数据块从freelist中去掉,当块由于dml操作free space大于pct_used设置的空

间时,该数据库块将被添加在freelist链表中。

 

简单描述tablespace / segment / extent / block之间的关系

tablespace:一个数据库划分为一个或多个逻辑单位,该逻辑单位成为表空间;每一个表空间可能包含一个或多个

segment;

segments:segment指在tablespace中为特定逻辑存储结构分配的空间。每一个段是由一个或多个extent组成。包括数据

段、索引段、回滚段和临时段。

extents:一个 extent 由一系列连续的 oracle blocks组成.oracle为通过extent 来给segment分配空间。

datablocks:oracle 数据库最小的i/o存储单位,一个data block对应一个或多个分配给data file的操作系统块。

table创建时,默认创建了一个data segment,每个data segment含有min extents指定的extents数,每个extent据据表空

间的存储参数分配一定数量的blocks

 

描述tablespace和datafile之间的关系

一个表空间可包含一个或多个数据文件。表空间利用增加或扩展数据文件扩大表空间,表空间的大小为组成该表空间的

数据文件大小的和。一个datafile只能属于一个表空间;

一个tablespace可以有一个或多个datafile,每个datafile只能在一个tablespace内, table中的数据,通过hash算法分布

在tablespace中的各个datafile中,tablespace是逻辑上的概念,datafile则在物理上储存了数据库的种种对象。

 

 

本地管理表空间和字典管理表空间的特点,assm有什么特点

本地管理表空间:(9i默认)空闲块列表存储在表空间的数据文件头。

特点:减少数据字典表的竞争,当分配和收缩空间时会产生回滚,不需要合并。

字典管理表空间:(8i默认)空闲块列表存储在数据库中的字典表里.

特点:片由数据字典管理,可能造成字典表的争用。存储在表空间的每一个段都会有不同的存储字句,需要合并相邻的

块;

本地管理表空间(locally managed tablespace简称lmt)

8i以后出现的一种新的表空间的管理模式,通过位图来管理表空间的空间使用。字典管理表空间(dictionary-managed

tablespace简称dmt)

8i以前包括以后都还可以使用的一种表空间管理模式,通过数据字典管理表空间的空间使用。动段空间管理(assm),

它首次出现在oracle920里有了assm,链接列表freelist被位图所取代,它是一个二进制的数组,

能够迅速有效地管理存储扩展和剩余区块(free block),因此能够改善分段存储本质,assm表空间上创建的段还有另

外一个称呼叫bitmap managed segments(bmb 段)。

回滚段的作用是什么

回滚段用于保存数据修改前的映象,这些信息用于生成读一致性数据库信息、在数据库恢复和rollback时使用。一个事

务只能使用一个回滚段。

事务回滚:当事务修改表中数据的时候,该数据修改前的值(即前影像)会存放在回滚段中,当用户回滚事务(

rollback)时,oracle将会利用回滚段中的数据前影像来将修改的数据恢复到原来的值。

事务恢复:当事务正在处理的时候,例程失败,回滚段的信息保存在undo表空间中,oracle将在下次打开数据库时利用

回滚来恢复未提交的数据。

读一致性:当一个会话正在修改数据时,其他的会话将看不到该会话未提交的修改。当一个语句正在执行时,该语句将

看不到从该语句开始执行后的未提交的修改(语句级读一致性)

当oracle执行select语句时,oracle依照当前的系统改变号(system change number-scn) 来保证任何前于当前scn的

未提交的改变不被该语句处理。可以想象:当一个长时间的查询正在执行时,若其他会话改变了该查询要查询的某个数

据块,oracle将利用回滚段的数据前影像来构造一个读一致性视图

 

日志的作用是什么

日志文件(log file)记录所有对数据库数据的修改,主要是保护数据库以防止故障,以及恢复数据时使用。其特点如

下:

a)每一个数据库至少包含两个日志文件组。每个日志文件组至少包含两个日志文件成员。

b)日志文件组以循环方式进行写操作。

c)每一个日志文件成员对应一个物理文件。

记录数据库事务,最大限度地保证数据的一致性与安全性

重做日志文件:含对数据库所做的更改记录,这样万一出现故障可以启用数据恢复,一个数据库至少需要两个重做日志文

归档日志文件:是重做日志文件的脱机副本,这些副本可能对于从介质失败中进行恢复很必要。

 

sga主要有那些部分,主要作用是什么

系统全局区(sga):是oracle为实例分配的一组共享缓冲存储区,用于存放数据库数据和控制信息,以实现对数据库数

据的管理和操作。

sga主要包括:

a)共享池(shared pool) :用来存储最近执行的SQL语句和最近使用的数据字典的数据。

b)数据缓冲区 (database buffer cache):用来存储最近从数据文件中读写过的数据。

c)重作日志缓冲区(redo log buffer):用来记录服务或后台进程对数据库的操作。

 

另外在sga中还有两个可选的内存结构:

d)javapool: 用来存储java代码。

e)largepool: 用来存储不与SQL直接相关的大型内存结构。备份、恢复使用。

ga:db_cache/shared_pool/large_pool/java_pool

db_cache:数据库缓存(block buffer)对于oracle数据库的运转和性能起着非常关键的作用,它占据oracle数据库sga

(系统共享内存区)的主要部分。oracle数据库通过使用lru算法,将最近访问的数据块存放到缓存中,从而优化对磁盘

数据的访问.

shared_pool:共享池的大小对于oracle 性能来说都是很重要的。共享池中保存数据字典高速缓冲和完全解析或编译的

的pl/SQL 块和SQL 语句及控制结构

large_pool:使用mts配置时,因为要在sga中分配uga来保持用户的会话,就是用large_pool来保持这个会话内存使用

rman做备份的时候,要使用large_pool这个内存结构来做磁盘i/o缓存器

java_pool:为java procedure预备的内存区域,如果没有使用java proc,java_pool不是必须的

oracle系统进程主要有哪些,作用是什么

数据写进程(dbwr):负责将更改的数据从数据库缓冲区高速缓存写入数据文件

日志写进程(lgwr):将重做日志缓冲区中的更改写入在线重做日志文件

系统监控 (smon): 检查数据库的一致性如有必要还会在数据库打开时启动数据库的恢复

进程监控 (pmon): 负责在一个oracle 进程失败时清理资源

检查点进程(ckpt):负责在每当缓冲区高速缓存中的更改永久地记录在数据库中时,更新控制文件和数据文件中的数据库

状态信息。

归档进程 (arch):在每次日志切换时把已满的日志组进行备份或归档

恢复进程 (reco): 保证分布式事务的一致性,在分布式事务中,要么同时commit,要么同时rollback;

作业调度器(cjq ): 负责将调度与执行系统中已定义好的job,完成一些预定义的工作.

 

oracle备份分类

逻辑备份:exp/imp 指定表的逻辑备份

物理备份:

热备份:alter tablespace begin/end backup;

冷备份:脱机备份(database shutdown)

rman备份

fullbackup/incremental backup(累积/差异)

物理备份

物理备份是最主要的备份方式。用于保证数据库在最小的数据库丢失或没有数据丢失的情况下得到恢复。

冷物理

冷物理备份提供了最简单和最直接的方法保护数据库因物理损坏丢失。建议在以下几种情况中使用。

对一个已经存在大最数据量的数据库,在晚间数据库可以关闭,此时应用冷物理备份。

对需对数据库服务器进行升级,(如更换硬盘),此时需要备份数据库信息,并在新的硬盘中恢复这些数据信息,建议

采用冷物理备份。

热物理

主要是指备份过程在数据库打开并且用户可以使用的情况下进行。需要执行热物理备份的情况有:

由于数据库性质要求不间断工作,因而此时只能采用热物理备份。

由于备份的要求的时间过长,而数据库只能短时间关闭时。

逻辑备份 (exp/imp)

逻辑备份用于实现数据库对象的恢复。但不是基于时间点可完全恢复的备份策略。只能作为联机备份和脱机备份的一种

补充。

完全逻辑备份

完全逻辑备份是将整个数据库导出到一个数据库的格式文件中,该文件可以在不同的数据库版本、操作系统和硬件平台

之间进行移植。

指定表的逻辑备份

通过备份工具,可以将指定的数据库表备份出来,这可以避免完全逻辑备份所带来的时间和财力上的浪费。

归档是什么含义

关于归档日志:oracle要将填满的在线日志文件组归档时,则要建立归档日志(archived redo log)。其对数据库备份

和恢复有下列用处:

数据库后备以及在线和归档日志文件,在操作系统和磁盘故障中可保证全部提交的事物可被恢复。

在数据库打开和正常系统使用下,如果归档日志是永久保存,在线后备可以进行和使用。

数据库可运行在两种不同方式下:noarchivelog方式或archivelog 方式

数据库在noarchivelog方式下使用时,不能进行在线日志的归档,

数据库在archivelog方式下运行,可实施在线日志的归档

归档是归档当前的联机redo日志文件。

svrmgr>alter system archive log current;

数据库只有运行在archivelog模式下,并且能够进行自动归档,才可以进行联机备份。有了联机备份才有可能进行完全

恢复。

如果一个表在2004-08-04 10:30:00 被drop,在有完善的归档和备份的情况下,如何恢复

9i 新增的flash back 应该可以;

logminer应该可以找出dml。

有完善的归档和备份,先归档当前数据,然后可以先恢复到删除的时间点之前,把drop 的表导出来,然后再恢复到最后

归档时间;

手工拷贝回所有备份的数据文件

SQL〉startup mount;

SQL〉alter database recover automatic until time '2004-08-04:10:30:00';

SQL〉alter database open resetlogs;

rman是什么,有何特点

rman(recoverymanager)是dba的一个重要工具,用于备份、还原和恢复oracle数据库, rman 可以用来备份和恢复数据

库文件、归档日志、控制文件、系统参数文件,也可以用来执行完全或不完全的数据库恢复。

rman有三种不同的用户接口:command line方式、gui 方式(集成在oem 中的备份管理器)、api 方式(用于集成到第

三方的备份软件中)。

具有如下特点:

1)功能类似物理备份,但比物理备份强大n倍;

2)可以压缩空块;

3)可以在块水平上实现增量;

4)可以把备份的输出打包成备份集,也可以按固定大小分割备份集;

5)备份与恢复的过程可以自动管理;

6)可以使用脚本(存在recovery catalog 中)

7)可以做坏块监测

 

七、 专题研究

重复数据处理

查看重复记录

select * from table_name where id in (select id from table_name groupby id having count(*)>1)

过滤掉所有多余的重复记录

select distinct * from table_name

或 select t.col1,t.col2 from table_name t group by t.col1,t.col2

 

删除重复记录

单表记录重复

1)delete from table_namewhere id not in (select max(id)from table_name group by col1,col2,...);

2)delete from table_namet where exists(select 1 from table_name where col1=t.col1 and col2=t. col2 and col3=t. col3 and id>t.id);

 

多表无效记录

一、两张关联表,删除主表中已经在副表中没有的信息

delete from table1 where not exists ( select 1 from table2 wheretable1.field1=table2.field1)

二、包括所有在 tablea 中但不在 tableb和tablec 中的行并消除所有重复行而派生出一个结果表(minus、intersect)

select a from tablea minus(select afrom tableb union select a from tablec)

查询表中列的值重复出现多次

注:查询表a中存在id重复3次以上的记录

select *from a t where exists (select 1 from a t1 where t.id = t1.idgroup by id having count(id) > 3);

重复数据处理实践

create table emp(

idint not null primary key,

name varchar (25),

age int

);

insert into empvalues(test_sequence.nextval,'zhang1',26);

insert into empvalues(test_sequence.nextval,'zhang2',27);

insert into empvalues(test_sequence.nextval,'zhang3',28);

insert into emp values(test_sequence.nextval,'zhang1',26);

insert into empvalues(test_sequence.nextval,'zhang2',27);

insert into empvalues(test_sequence.nextval,'zhang3',29);

insert into empvalues(test_sequence.nextval,'wang2',26);

insert into emp values(test_sequence.nextval,'wang1',22);

使用不同的方法查询出重复的数据

--解法一:查询重复记录的一个方法就是分组统计

select * from emp where name in (select name from emp group by name having count(*)>1);使用in 或者exists

--查询出重复的名称及重复次数

方法一、select sum(1)as sig, name from emp group by name having sum(1) > 1;

方法二、select name, count(*) from empgroup by name having count(*)>1;

count(*)的效率比sum(1)要高

 

--解法二:如果对每个名字都和原表进行比较,大于2个人名字与这条记录相同的就是合格的,就有:

select * from emp where ( select count(*)from emp e where e.name = emp.name )>1;

 

--解法三:如果有另外一个名字相同的人工号不与他相同那么这条记录符合要求:

select * from emp where exists (select *from emp e where e.name = emp.name and

e.id<>emp.id);

 

解法四:思路同解法三

select distinct emp.* from emp inner join emp e onemp.name=e.name and emp.id<>e.id;

 

查询过滤掉所有多余的重复记录

--解法一:通过distinct、group by过滤重复

select distinct name,age from emp;

select name,age from emp group by name,age;

 

--解法二:使用临时表

--不推荐

select distinct * into #tmp from emp;

delete from emp;

insert into emp select * from #tmp;

 

--解法三:使用rowid

--高效,name列使用了索引。

select * from emp a where a.rowid = (selectmin(b.rowid) from emp b where a.name = b.name);

--普通

select a.* from emp a,(select min(b.rowid)row_id from emp b group by b.name) b where a.rowid = b.row_id;

删除SQL表中重复的记录

注:除id值不一样外其它字段都一样,每两行记录重复

delete from emp t where exists(select 1from emp where name = t.name and age = t.age and t.id < id)

 

基于oracle数据背景的重复记录删除

综合评价:★★★

(1).在oracle中,每一条记录都有一个rowid,rowid在整个数据库中是唯一的,rowid确定了每条记录是在oracle中的哪一个数据文件、块、行上。

(2).在重复的记录中,可能所有列的内容都相同,但rowid不会相同,所以只要确定出重复记录中那些具有最大rowid的就可以了,其余全部删除。

重复记录判断的标准是:

c1,c10和c20这三列的值都相同才算是重复记录,并且保存最新的记录。

(1).适用于有大量重复记录的情况(在c1,c10和c20列上建有索引的时候,用以下语句效率会很高):

方法一、delete from cz where(c1,c10,c20) in (select c1,c10,c20 from cz group by c1,c10,c20 havingcount(*)>1) and rowid not in selectmin(rowid) from cz group by c1,c10,c20 having count(*)>1);

方法二、delete from cz where rowid not in(select min(rowid) fromcz group by c1,c10,c20);

 

(2).适用于有少量重复记录的情况(注意,对于有大量重复记录的情况,用以下语句效率会很低):

方法一、delete from cz a where a.rowid!=(select max(rowid) from cz b wherea.c1=b.c1 and a.c10=b.c10 and a.c20=b.c20);

方法二、delete from cz a where a.rowid<(select max(rowid) from cz b wherea.c1=b.c1 and a.c10=b.c10 and a.c20=b.c20);

方法三、delete from cz a where rowid <(select max(rowid) from cz wherec1=a.c1 and c10=a.c10 and c20=a.c20);

详细教程:http://www.csdn.net/article/2010-08-17/278287

oracle取随机数据

注:随机取出n条数据

1)select * from (select * from tablename order bysys_guid()) where rownum < n;

2)select * from (select * from tablename order by dbms_random.value)where rownum< n

3) select * from (select *from tablename sample(n) order by trunc(dbms_random.value(0,1000))) where rownum < n;

说明: sample(n)含义为检索表中的n%数据,sample值应该在[0.000001,99.999999]之间。

其中 sys_guid() 和 dbms_random.value都是内部函数。

oracle中一般获取随机数的方法是

select trunc(dbms_random.value(0,1000)) from dual; (0-1000的整数)

select dbms_random.value(0, 1000) fromdual; (0-1000的浮点数)

oracle 关键字case的用法

case匹配语句

case expression

when value then statement

[when value then statement ]...

[else statement [, statement ]... ]

end case;

case搜索语句

case

when (boolean_condition1) then action1;

when (boolean_condition2) then action2;

when (boolean_condition3) then action3;

……

else action;

end case;

select case 语句

有一张表,里面有3个字段:语文,数学,英语。其中有3条记录分别表示语文70分,数学80分,英语58分,请用一条SQL语句查询出这三条记录并按以下条件显示出来。大于或等于80表示优秀,大于或等于60表示及格,小于60分表示不及格。

显示格式:

语文 数学 英语

及格 优秀 不及格

------------------------------------------

select

(case when 语文>=80 then '优秀'

when 语文>=60 then '及格'

else '不及格') as 语文,

(case when 数学>=80 then'优秀'

when 数学>=60 then '及格'

else '不及格') as 数学,

(case when 英语>=80 then'优秀'

when 英语>=60 then '及格'

else '不及格') as 英语

from table

oracle日期函数

一、日程安排提前五分钟提醒(oracle 日期函数)

select * from 日程安排 where datediff(‘minute’,开始时间,getdate())>5

 

二、请取出tb_send表中日期(sendtime字段)为当天的所有记录?(sendtime字段为date型,包含日期与时间)

select * from tb_send t where to_char(t.sendtime,’yyyy-mm-dd’) = to_char(sysdate,’yyyy-mm-dd’)

 

oracle trunc()函数的用法

综合评价:★★

trunc() 操作日期

1.select trunc(sysdate) from dual --2011-3-18 今天的日期为2011-3-18

2.select trunc(sysdate, 'mm') from dual --2011-3-1 返回当月第一天.

3.select trunc(sysdate,'yy') from dual --2011-1-1 返回当年第一天

4.select trunc(sysdate,'dd') from dual --2011-3-18 返回当前年月日

5.select trunc(sysdate,'yyyy') from dual --2011-1-1 返回当年第一天

6.select trunc(sysdate,'d') from dual --2011-3-13 (星期天)返回当前星期的第一天

7.select trunc(sysdate, 'hh') from dual --2011-3-18 14:00:00 当前时间为14:41

8.select trunc(sysdate, 'mi') from dual --2011-3-18 14:41:00 trunc()函数没有秒的精确

 

trunc() 操作数字

注:trunc(number,num_digits) number 需要截尾取整的数字。 num_digits 用于指定取整精度的数字。num_digits 的默认值为 0。trunc()函数截取时不进行四舍五入

9.select trunc(123.458) from dual --123

10.select trunc(123.458,0) from dual --123

11.select trunc(123.458,1) from dual --123.4

12.select trunc(123.458,-1) from dual --120

13.select trunc(123.458,-4) from dual --0

14.select trunc(123.458,4) from dual --123.458

15.select trunc(123) from dual --123

16.select trunc(123,1) from dual --123

17.select trunc(123,-1) from dual –120

 

计算当月的天数

综合评价:★★

select datepart(dd,dateadd(dd,-1,dateadd(mm,1,cast(cast(year(getdate()) as varchar)+'-'+cast(month(getdate()) as varchar)+'-01' as datetime))))

1.求当年天数

selectadd_months(trunc(sysdate, 'yyyy'), 12) - trunc(sysdate, 'yyyy') days from dual

2.求当月天数

selectto_number(to_char(last_day(trunc(sysdate)),'dd')) from dual

3.求指定月天数

select to_number(add_months(trunc(to_date('2013-06-1710:09:02',

'yyyy-fmmm-ddhh24:mi:ss'), 'mm'), 1) - trunc(to_date('2013-06-17 10:09:02',

'yyyy-fmmm-ddhh24:mi:ss'), 'mm')) from dual;

 

oracle临时表

oracle多行数据分组拼接

只用一条SQL语句,要求从左表查询出右表

lefttable: righttable:

id name id name

---------- ------------------

1 a5 1 a5,a8,af....

2 a8 2 b5,b3,bd....

3 af 3 c3,ck,ci....

4 b5

5 b3

6 bd

7 c3

8 ck

9 ci

解答:

select replace(wmsys.wm_concat(t.name),',',',')from lefttable t group by substr(t.name,0,1);

oracle分析函数

oracle 常用函数,nvl、递归、字符串操作、日期操作、数字操作

case when、正则表达式操作

oracle分页与rownum

参照表:test_tb_grade

题目:查询6到10条数据。

解法一、注:采用rownum关键字(三层嵌套)

select * from (

select t.*,rownum rn from

(select * from test_tb_grade order by user_id) t where rownum <= 10)

where rn >=6;

 

解法二注:采用row_number解析函数进行分页(效率更高)

select * from (

select t.*, row_number() over(order by t.user_id) rn from test_tb_grade t)

where rn between 6 and 10;

行转列,列转行

行转列

create table test_tb_grade -- 学生语文、数学、英语成绩统计

(

user_id number(10) not nullprimary key,

user_name varchar2(20),

course varchar2(20),

score float

);

create sequence test_sequence;

insert into test_tb_gradevalues(test_sequence.nextval,'m','c',78);

insert into test_tb_gradevalues(test_sequence.nextval,'m','m',95);

insert into test_tb_gradevalues(test_sequence.nextval,'m','e',81);

insert into test_tb_gradevalues(test_sequence.nextval,'z','c',97);

insert into test_tb_gradevalues(test_sequence.nextval,'z','m',78);

insert into test_tb_gradevalues(test_sequence.nextval,'z','e',91);

insert into test_tb_gradevalues(test_sequence.nextval,'l','c',80);

insert into test_tb_grade values(test_sequence.nextval,'l','m',55);

insert into test_tb_gradevalues(test_sequence.nextval,'l','e',75);

insert into test_tb_gradevalues(test_sequence.nextval,'v','c',49);

insert into test_tb_gradevalues(test_sequence.nextval,'v','m',63);

insert into test_tb_grade values(test_sequence.nextval,'v','e',70);

 

insert into test_tb_gradevalues(test_sequence.nextval,'k','e',53);

insert into test_tb_gradevalues(test_sequence.nextval,'k','m',58);

insert into test_tb_gradevalues(test_sequence.nextval,'k','c',59);

insert into test_tb_gradevalues(test_sequence.nextval,'k','c',59);

commit;

列出不同科目下学生对应的成绩,如图

 

注:sum聚集函数也可以用max、min、avg等其他聚集函数替代。不同聚合函数得到结果根据数据会有稍微的差异

select user_name,

sum(decode(course,'c',score,null)) aschinese,

sum(decode(course,'m',score,null)) as math,

sum(decode(course,'e',score,null)) as english

from test_tb_grade

group by user_name

order by 1;

 

列转行

create table test_tb_grade2 (

gidnumber(10) primary key,

sname varchar2(20),

cn_score float,

math_score float,

en_score float

);

insert into test_tb_grade2 values(test_sequence.nextval,'m',70,65,60);

insert into test_tb_grade2 values(test_sequence.nextval,'l',74,83,76);

insert into test_tb_grade2 values(test_sequence.nextval,'v',80,68,89);

insert into test_tb_grade2 values (test_sequence.nextval,'k',80,83,82);

insert into test_tb_grade2 values(test_sequence.nextval,'z',58,86,80);

commit;

查询结果如图:

 

需要实现到如下效果:

 

方法一、union all

select t2.sname,'chinese' as course,t2.cn_score as score from test_tb_grade2 t2 -- 语文

union all

select t2.sname,'math' as course,t2.math_score as score from test_tb_grade2 t2 -- 数学

union all

select t2.sname,'english' as course,t2.en_score as score from test_tb_grade2 t2 -- 英语

 

方法二、model (复杂)

方法三、collection(涉及到数组、集合、对象)

分组统计与取值

分组统计

实现对各门功课的不同分数段的学生进行统计(0-60,60-70,70-80..)

方法一、select t2.course,count(t2.course),'0-60'as ts from (

select t.course,t.score from test_tb_gradet group by t.course,t.score having t.score<60 ) t2 group by t2.course

union all

select t2.course,count(t2.course),'60-70'asts from (

select t.course,t.score from test_tb_gradet group by t.course,t.score having t.score>60 and t.score<=70 ) t2 groupby t2.course….

 

方法二、select t.course,

case

when t.score<= 60 then '0-60'

when 60< t.score and t.score <=70 then '60-70'

when 70< t.score and t.score <=80 then '70-80'

when 80< t.score then '80-100'

else 'error' end as score_cp,

count(t.course)

fromtest_tb_grade t group by t.course

得到如下视图

 

 

取前n条记录

参照表:test_tb_grade

题目:查询出每个学生分数最高的两门课(允许并列第二)

解法一、注:使用左联接和分组函数

select t1.user_id,t1.user_name,t1.course,t1.score from test_tb_grade t1 left

join test_tb_grade t2 on t1.user_name =t2.user_name and t1.score < t2.score

group by t1.user_id,t1.user_name,t1.course,t1.score

having count(t2.user_id) < 2;

 

解法二、注:oracle分析函数(不出现并列)

select * from (

select user_id, user_name, course, score, row_number()over(partition by user_nameorder by score desc) rn from test_tb_grade) where rn <= 2;

 

解法三、注:使用关联子查询

select * from test_tb_grade t where 2 > (

select count(*) from test_tb_grade where user_name= t.user_name and t.score < score);

取最大记录

参照表:test_tb_grade

题目:查询每个单科分数最高的信息

解法一、注:分组函数不能放到where后面

select * from test_tb_grade t where t.score=

( select max(score) from test_tb_gradewhere course = t.course) order by t.user_name;

 

解法二、注:使用notexists 关键字

select * from test_tb_grade t where not exists

(select 1 from test_tb_grade where course = t. course andt.score < score);

 

解法三、注:使用内连接查询

select t.* from test_tb_grade t,

(select course,max(score) score fromtest_tb_grade group by course) t1

where t. course = t1. course and t.score =t1.score;

 

解法四、注:使用关联子查询

select t.* from test_tb_grade t where 1> (select count(*) from test_tb_grade where user_name = t.user_name andt.score < score);

八、 练习题

一、s(s#,sn,sd,sa) s#,sn,sd,sa分别代表学号,学员姓名,所属单位,学员年龄

c(c#,cn) c#,cn分别代表课程编号,课程名称

sc(s#,c#,g) s#,c#,g分别代表学号,所选的课程编号,学习成绩

 

(1)使用标准SQL嵌套语句查询选修课程名称为’税收基础’的学员学号和姓名?

答案:select s# ,sn from s where s# in(select s# from c,sc wherec.c#=sc.c# and cn=’税收基础’)

 

selects.s#, s.sn from sc,s,c where c.c# = sc.c# and sc.s# = s.s# and c.cn = ‘税收基础’;

 

selects#, sn from s where exists (select 1 from c,sc where c.c# = sc.c# and c.cn = ‘税收基础’ andsc.s# = s.s#);

 

(2) 使用标准SQL嵌套语句查询选修课程编号为’c2’的学员姓名和所属单位?

答:select sn,sd from s,sc where s.s#=sc.s# and sc.c#=’c2’;

 

(3) 使用标准SQL嵌套语句查询不选修课程编号为’c5’的学员姓名和所属单位?

答:select sn,sd from s where s# not in(select s# from sc where c#=’c5’)

 

(4)查询选修了课程的学员人数

selectcount(distinct s#) 学员人数 from sc;

(5) 查询选修课程超过5门的学员学号和所属单位

selectsn,sd from s where s# in(select s# from sc group by s# having count(distinctc#)>5)

selects#, sd from s t where exists (select 1 from sc where t.s# = s# group by #having count(distinct c#)>5);

 

(6)查询a(id,name)表中第31至40条记录,id作为主键可能是不是连续增长的列

SQLserver:

select top 10 * from (select top 40 * from a order by id)order by id desc;

mySQL:

select * from a limit 31,40;

oracle:rownum 始终从下一个整数开始,第一值是1,如果前9个值可以确定,那下一个值就10(rownum 是伪列)

select * from (select t.*, rownum rn from at where rownum <= 40 order by id) t1 where t1.rn => 31;

 

(7)查询表a中存在id重复三次以上的记录

select *from a t where exists (select 1 from a t1 where t1.id = t.id group by id havingcount(id) > 3);

(8)游标使用

-- 声明游标;CURSOR cursor_name IS select_statement

--For 循环游标

--(1)定义游标

--(2)定义游标变量

--(3)使用for循环来使用这个游标

declare

--类型定义

cursor c_job

is

select empno,ename,job,sal

from emp

where job='MANAGER';

--定义一个游标变量v_cinfoc_emp%ROWTYPE ,该类型为游标c_emp中的一行数据类型

c_row c_job%rowtype;

begin

for c_row in c_job loop

dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal);

end loop;

end;

 

 

 

--Fetch游标

--使用的时候必须要明确的打开和关闭

 

declare

--类型定义

cursor c_job

is

select empno,ename,job,sal

from emp

where job='MANAGER';

--定义一个游标变量

c_row c_job%rowtype;

begin

open c_job;

loop

--提取一行数据到c_row

fetch c_job into c_row;

--判读是否提取到值,没取到值就退出

--取到值c_job%notfound 是false

--取不到值c_job%notfound 是true

exit when c_job%notfound;

dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal);

end loop;

--关闭游标

close c_job;

end;

 

--1:任意执行一个update操作,用隐式游标sql的属性%found,%notfound,%rowcount,%isopen观察update语句的执行情况。

begin

update emp set ENAME='ALEARK' WHEREEMPNO=7469;

if sql%isopen then

dbms_output.put_line('Openging');

else

dbms_output.put_line('closing');

end if;

if sql%found then

dbms_output.put_line('游标指向了有效行');--判断游标是否指向有效行

else

dbms_output.put_line('Sorry');

end if;

if sql%notfound then

dbms_output.put_line('Also Sorry');

else

dbms_output.put_line('Haha');

end if;

dbms_output.put_line(sql%rowcount);

exception

when no_data_found then

dbms_output.put_line('Sorry No data');

when too_many_rows then

dbms_output.put_line('Too Many rows');

end;

declare

empNumber emp.EMPNO%TYPE;

empName emp.ENAME%TYPE;

begin

if sql%isopen then

dbms_output.put_line('Cursor is opinging');

else

dbms_output.put_line('Cursor is Close');

end if;

if sql%notfound then

dbms_output.put_line('No Value');

else

dbms_output.put_line(empNumber);

end if;

dbms_output.put_line(sql%rowcount);

dbms_output.put_line('-------------');

 

select EMPNO,ENAME into empNumber,empName fromemp where EMPNO=7499;

dbms_output.put_line(sql%rowcount);

 

if sql%isopen then

dbms_output.put_line('Cursor is opinging');

else

dbms_output.put_line('Cursor is Closing');

end if;

if sql%notfound then

dbms_output.put_line('No Value');

else

dbms_output.put_line(empNumber);

end if;

exception

when no_data_found then

dbms_output.put_line('No Value');

when too_many_rows then

dbms_output.put_line('too many rows');

end;

 

 

 

--2,使用游标和loop循环来显示所有部门的名称

--游标声明

declare

cursor csr_dept

is

--select语句

select DNAME

from Depth;

--指定行指针,这句话应该是指定和csr_dept行类型相同的变量

row_dept csr_dept%rowtype;

begin

--for循环

for row_dept in csr_dept loop

dbms_output.put_line('部门名称:'||row_dept.DNAME);

end loop;

end;

 

 

--3,使用游标和while循环来显示所有部门的的地理位置(用%found属性)

declare

--游标声明

cursor csr_TestWhile

is

--select语句

select LOC

from Depth;

--指定行指针

row_loc csr_TestWhile%rowtype;

begin

--打开游标

open csr_TestWhile;

--给第一行喂数据

fetch csr_TestWhile into row_loc;

--测试是否有数据,并执行循环

while csr_TestWhile%found loop

dbms_output.put_line('部门地点:'||row_loc.LOC);

--给下一行喂数据

fetch csr_TestWhile into row_loc;

end loop;

close csr_TestWhile;

end;

select *from emp

 

 

 

 

--4,接收用户输入的部门编号,用for循环和游标,打印出此部门的所有雇员的所有信息(使用循环游标)

--CURSORcursor_name[(parameter[,parameter],...)] IS select_statement;

--定义参数的语法如下:Parameter_name [IN] data_type[{:=|DEFAULT} value]

 

declare

CURSOR

c_dept(p_deptNo number)

is

select * from emp where emp.depno=p_deptNo;

r_emp emp%rowtype;

begin

for r_emp in c_dept(20) loop

dbms_output.put_line('员工号:'||r_emp.EMPNO||'员工名:'||r_emp.ENAME||'工资:'||r_emp.SAL);

end loop;

end;

select *from emp

--5:向游标传递一个工种,显示此工种的所有雇员的所有信息(使用参数游标)

declare

cursor

c_job(p_job nvarchar2)

is

select * from emp where JOB=p_job;

r_job emp%rowtype;

begin

for r_job in c_job('CLERK') loop

dbms_output.put_line('员工号'||r_job.EMPNO||' '||'员工姓名'||r_job.ENAME);

end loop;

end;

SELECT *FROM EMP

 

--6:用更新游标来为雇员加佣金:(用if实现,创建一个与emp表一摸一样的emp1表,对emp1表进行修改操作),并将更新前后的数据输出出来

create table emp1 as select * from emp;

 

declare

cursor

csr_Update

is

select * from emp1 for update OF SAL;

empInfo csr_Update%rowtype;

saleInfo emp1.SAL%TYPE;

begin

FOR empInfo IN csr_Update LOOP

IF empInfo.SAL<1500 THEN

saleInfo:=empInfo.SAL*1.2;

elsif empInfo.SAL<2000 THEN

saleInfo:=empInfo.SAL*1.5;

elsif empInfo.SAL<3000 THEN

saleInfo:=empInfo.SAL*2;

END IF;

UPDATE emp1 SET SAL=saleInfo WHERE CURRENT OFcsr_Update;

END LOOP;

END;

 

--7:编写一个PL/SQL程序块,对名字以‘A’或‘S’开始的所有雇员按他们的基本薪水(sal)的10%给他们加薪(对emp1表进行修改操作)

declare

cursor

csr_AddSal

is

select * from emp1 where ENAME LIKE 'A%' ORENAME LIKE 'S%' for update OF SAL;

r_AddSal csr_AddSal%rowtype;

saleInfo emp1.SAL%TYPE;

begin

for r_AddSal in csr_AddSal loop

dbms_output.put_line(r_AddSal.ENAME||'原来的工资:'||r_AddSal.SAL);

saleInfo:=r_AddSal.SAL*1.1;

UPDATE emp1 SET SAL=saleInfo WHERE CURRENT OFcsr_AddSal;

end loop;

end;

--8:编写一个PL/SQL程序块,对所有的salesman增加佣金(comm)500

declare

cursor

csr_AddComm(p_job nvarchar2)

is

select * from emp1 where JOB=p_job FOR UPDATEOF COMM;

r_AddComm emp1%rowtype;

commInfo emp1.comm%type;

begin

for r_AddComm in csr_AddComm('SALESMAN') LOOP

commInfo:=r_AddComm.COMM+500;

UPDATE EMP1 SET COMM=commInfo where CURRENT OFcsr_AddComm;

END LOOP;

END;

 

--9:编写一个PL/SQL程序块,以提升2个资格最老的职员为MANAGER(工作时间越长,资格越老)

--(提示:可以定义一个变量作为计数器控制游标只提取两条数据;也可以在声明游标的时候把雇员中资格最老的两个人查出来放到游标中。)

declare

cursor crs_testComput

is

select * from emp1 order by HIREDATE asc;

--计数器

top_two number:=2;

r_testComput crs_testComput%rowtype;

begin

open crs_testComput;

FETCH crs_testComput INTO r_testComput;

while top_two>0 loop

dbms_output.put_line('员工姓名:'||r_testComput.ENAME||' 工作时间:'||r_testComput.HIREDATE);

--计速器减一

top_two:=top_two-1;

FETCH crs_testComput INTO r_testComput;

end loop;

close crs_testComput;

end;

 

 

--10:编写一个PL/SQL程序块,对所有雇员按他们的基本薪水(sal)的20%为他们加薪,

--如果增加的薪水大于300就取消加薪(对emp1表进行修改操作,并将更新前后的数据输出出来)

declare

cursor

crs_UpadateSal

is

select * from emp1 for update of SAL;

r_UpdateSal crs_UpadateSal%rowtype;

salAdd emp1.sal%type;

salInfo emp1.sal%type;

begin

for r_UpdateSal in crs_UpadateSal loop

salAdd:= r_UpdateSal.SAL*0.2;

if salAdd>300 then

salInfo:=r_UpdateSal.SAL;

dbms_output.put_line(r_UpdateSal.ENAME||': 加薪失败。'||'薪水维持在:'||r_UpdateSal.SAL);

else

salInfo:=r_UpdateSal.SAL+salAdd;

dbms_output.put_line(r_UpdateSal.ENAME||': 加薪成功.'||'薪水变为:'||salInfo);

end if;

update emp1 set SAL=salInfo where current ofcrs_UpadateSal;

end loop;

end;

 

--11:将每位员工工作了多少年零多少月零多少天输出出来

--近似

--CEIL(n)函数:取大于等于数值n的最小整数

--FLOOR(n)函数:取小于等于数值n的最大整数

--truc的用法http://publish.it168.com/2005/1028/20051028034101.shtml

declare

cursor

crs_WorkDay

is

select ENAME,HIREDATE,trunc(months_between(sysdate, hiredate) / 12) AS SPANDYEARS,

trunc(mod(months_between(sysdate, hiredate),12)) AS months,

trunc(mod(mod(sysdate - hiredate, 365), 12))as days

from emp1;

r_WorkDay crs_WorkDay%rowtype;

begin

for r_WorkDay in crs_WorkDay loop

dbms_output.put_line(r_WorkDay.ENAME||'已经工作了'||r_WorkDay.SPANDYEARS||'年,零'||r_WorkDay.months||'月,零'||r_WorkDay.days||'天');

end loop;

end;

 

--12:输入部门编号,按照下列加薪比例执行(用CASE实现,创建一个emp1表,修改emp1表的数据),并将更新前后的数据输出出来

--deptno raise(%)

-- 10 5%

-- 2010%

-- 3015%

-- 4020%

-- 加薪比例以现有的sal为标准

--CASEexpr WHEN comparison_expr THEN return_expr

--[,WHEN comparison_expr THEN return_expr]... [ELSE else_expr] END

declare

cursor

crs_caseTest

is

select * from emp1 for update of SAL;

r_caseTest crs_caseTest%rowtype;

salInfo emp1.sal%type;

begin

for r_caseTest in crs_caseTest loop

case

when r_caseTest.DEPNO=10

THEN salInfo:=r_caseTest.SAL*1.05;

when r_caseTest.DEPNO=20

THEN salInfo:=r_caseTest.SAL*1.1;

when r_caseTest.DEPNO=30

THEN salInfo:=r_caseTest.SAL*1.15;

when r_caseTest.DEPNO=40

THEN salInfo:=r_caseTest.SAL*1.2;

end case;

update emp1 set SAL=salInfo where current ofcrs_caseTest;

end loop;

end;

 

--13:对每位员工的薪水进行判断,如果该员工薪水高于其所在部门的平均薪水,则将其薪水减50元,输出更新前后的薪水,员工姓名,所在部门编号。

--AVG([distinct|all]expr) over (analytic_clause)

---作用:

--按照analytic_clause中的规则求分组平均值。

--分析函数语法:

--FUNCTION_NAME(,...)

--OVER

--()

--PARTITION子句

--按照表达式分区(就是分组),如果省略了分区子句,则全部的结果集被看作是一个单一的组

select * from emp1

DECLARE

CURSOR

crs_testAvg

IS

select EMPNO,ENAME,JOB,SAL,DEPNO,AVG(SAL) OVER(PARTITION BY DEPNO ) AS DEP_AVG

FROM EMP1 for update of SAL;

r_testAvg crs_testAvg%rowtype;

salInfo emp1.sal%type;

begin

for r_testAvg in crs_testAvg loop

if r_testAvg.SAL>r_testAvg.DEP_AVG then

salInfo:=r_testAvg.SAL-50;

end if;

update emp1 set SAL=salInfo where current ofcrs_testAvg;

end loop;

end;

九、 存在的问题

1) 存储过程、游标使用

2) Oracle常用函数、分析函数、oracle 递归查询

3) 基于oracle的SQL优化

4) 数据库设计原则、设计规范学习

5) 教材:数据库设计与开发、数据库设计入门。。。。

点击复制链接 与好友分享!回本站首页
相关TAG标签 基础知识 数据库
上一篇:遇到ORACLE错误1658
下一篇:Oracle11g的dataguard创建中ORA-01665解决过程
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做实用的IT技术学习网站