写出高效的sql语句

sql于是的高效的写出,在我们自己书写的时候,需要从以下的几个方向注意:

1. IS NULL 与 IS NOT NULL

避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引。 

对于单列索引,如果列包含空值,索引中将不存在此记录; 

对于复合索引,如果每个列都为空,索引中同样不存在此记录; 

如果至少有一个列不为空,则记录存在于索引中。 

 

举例: 

如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null), 

ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入), 

然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空。 

因此你可以插入1000 条具有相同键值的记录,当然它们都是空! 

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

不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。

任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。

注意:如果我们必须要用 is null,又需要提供查询效率 可以用函数索引

实例如下:

create table test_date (name varchar2(20),day date);

insert into test_date(name ,day) values ('lucy',null);

insert into test_date(name ,day) values ('jony',null);

insert into test_date(name,day) values ('james',sysdate);

select * from test_date;

--创建decode函数索引来代替 

create   index  finx_day on  test_date(decode(day,null,'N', 'Y'))

--使用decode判断来代替is null判断

select * from test_date a where  decode(day,null,'N','Y') = 'N'

 

SELECT STATEMENT, GOAL = CHOOSE                                        

TABLE ACCESS FULL        Object owner=SP2        Object name=TEST_DATE                        

注意 要使用cost 才会使用function index

analyze table test_date compute statistics for table for all indexes for all indexed columns;

   

select * from test_date a where  decode(day,null,'N','Y') = 'N'

SELECT STATEMENT, GOAL = CHOOSE                        Cost=1        Cardinality=1        Bytes=11

TABLE ACCESS BY INDEX ROWID     Object owner=SP2        Object name=TEST_DATE        Cost=1        Cardinality=1        Bytes=11

  INDEX RANGE SCAN        Object owner=SP2        Object name=FINX_DAY        Cost=2        Cardinality=1    

2. 联接列

对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工。

下面是一个采用联接查询的SQL语句,

select * from employs where  

first_name||’ ’||last_name ='Beill Cliton'

上面这条语句完全可以查询出是否有Bill Cliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。

当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。

Select * from employee where

first_name ='Beill' and last_name ='Cliton'

遇到下面这种情况又如何处理呢?如果一个变量(name)中存放着Bill Cliton这个员工的姓名,对于这种情况我们又如何避免全程遍历,使用索引呢?可以使用一个函数,将变量name中的姓和名分开就可以了,但是有一点需要注意,这个函数是不能作用在索引列上。下面是SQL查询脚本:

select * from employee

where

first_name = SUBSTR('&&name',1,INSTR('&&name',' ')-1)

and

last_name = SUBSTR('&&name',INSTR('&&name’,' ')+1)

注:substr和instr用法

1)、substr(string string, int a, int b) 
参数1:string 要处理的字符串 
参数2:a 截取字符串的开始位置(起始位置是0) 
参数3:b 截取的字符串的长度(而不是字符串的结束位置) 
例如: 
substr("ABCDEFG", 0); //返回:ABCDEFG,截取所有字符 
substr("ABCDEFG", 2); //返回:CDEFG,截取从C开始之后所有字符 
substr("ABCDEFG", 0, 3); //返回:ABC,截取从A开始3个字符 
substr("ABCDEFG", 0, 100); //返回:ABCDEFG,100虽然超出预处理的字符串最长度,但不会影响返回结果,系统按预处理字符串最大数量返回。 
substr("ABCDEFG", 0, -3); //返回:EFG,注意参数-3,为负值时表示从尾部开始算起,字符串排列位置不变。 

2)、instr的语法如下:
instr( string1, string2 [, start_position [, nth_appearance ] ] )

函数说明:
该函数返回参数string2在参数string1中的位置。
start_position参数表示将从string1的第几个字符开始来查找string2。该参数为可选参数,如果省略,那么默认为1。instr函数默认的查找顺序为从左到右。当该参数为负数的时候,则从右边开始查找。

nth_appearance表示返回第几次出现的string2的位置。该参数为可选参数,如果省略则默认为1,也就是返回首次出现string2的位置。

示例:
SELECT INSTR(‘CORPORATE FLOOR’, ‘OR’, -1, 1) “aaa” FROM DUAL;

aaa
———-
14

3. 带通配符(%)的like语句

同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句:

select * from employee where last_name like '%cliton%'

这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:

select * from employee where last_name like 'c%'

4. Order by语句

ORDER BY语句决定了Oracle如何将返回的查询结果排序。Order by语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。

仔细检查order by语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by子句中使用表达式。

5. NOT 的理想替代方案

我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子:

... where not (status ='VALID')

如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>;)运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例:

... where status <>'INVALID'

再看下面这个例子:

select * from employee where salary<>3000;

对这个查询,可以改写为不使用NOT:

select * from employee where salary<3000 or salary>3000;

虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。

6. IN和EXISTS(下面有个重复的)

有时候会将一列和一系列值相比较。最简单的办法就是在where子句中使用子查询。在where子句中可以使用两种格式的子查询。

第一种格式是使用IN操作符:

... where column in(select * from ... where ...);

第二种格式是使用EXIST操作符:

... where exists (select 'X' from ...where ...);

我相信绝大多数人会使用第一种格式,因为它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle中可以几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。

第二种格式中,子查询以‘select 'X'开始。运用EXISTS子句不管子查询从表中抽取什么数据它只查看where子句。这样优化器就不必遍历整个表而仅根据索引就可完成工作(这里假定在where语句中使用的列存在索引)。相对于IN子句来说,EXISTS使用相连子查询,构造起来要比IN子查询困难一些。

通过使用EXIST,Oracle系统会首先检查主查询,然后运行子查询直到它找到第一个匹配项,这就节省了时间。Oracle系统在执行IN子查询时,首先执行子查询,并将获得的结果列表存放在在一个加了索引的临时表中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中以后再执行主查询。这也就是使用EXISTS比使用IN通常查询速度快的原因。

同时应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),NOT EXISTS要比NOT IN查询效率更高

7、Where子句中的连接顺序: 

(CBO根据对表做的分析进行执行,RBO根据索引)

ORACLE采用自下而上的顺序解析WHERE子句。 

根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。

注:RBO CBO 模式

Oracle的优化器有两种优化方式,即基于规则的优化方式(Rule-Based Optimization,简称为RBO)和基于代价的优化方式(Cost-Based Optimization,简称为CBO),在Oracle8及以后的版本,Oracle强列推荐用CBO的方式

  RBO方式:优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。

  CBO方式:它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。

  Examda提示:主索引不一定就是优的,比如一个表只有两行数据,一次IO就可以完成全表的检索,而此时走索引时则需要两次IO,这时全表扫描(full table scan)是最好

  优化模式包括Rule、Choose、First rows、All rows四种方式:

  Rule:基于规则的方式。

  Choolse:默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。

  First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。

  All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走RBO的方式。

  设定选用哪种优化模式:

  A、Instance级别我们可以通过在initSID.ora文件中设定OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS如果没设定OPTIMIZER_MODE参数则默认用的是Choose方式。

  B、Sessions级别通过ALTER SESSION SET OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS来设定。

  C、语句级别用Hint(/*+ ... */)来设定

  为什么表的某个字段明明有索引,但执行计划却不走索引?

  1、优化模式是all_rows的方式

  2、表作过analyze,有统计信息(最可能的就是统计信息有误)

  3、表很小,上文提到过的,Oracle的优化器认为不值得走索引。

  我们可以查看一下一个表或索引是否是统计信息

  SELECT * FROM user_tables

  WHERE table_name=<table_name>

  AND num_rows is not null;

  SELECT * FROM user_indexes

  WHERE table_name=<table_name>

  AND num_rows is not null;

  当我们使用CBO的方式,就应当及时去更新表和索引的统计信息,以免生形不切合实的执行计划。

  ANALYZE table table_name COMPUTE STATISTICS;

ANALYZE INDEX index_name ESTIMATE STATISTICS;

 

8、Select子句中避免使用 “ * ”: 

当你想在select子句中列出所有的column时,使用动态SQL列引用 ‘*' 是一个方便的方法。 

不幸的是,这是一个非常低效的方法。 

实际上,ORACLE在解析的过程中,会将 '*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。

9、减少访问数据库的次数: 

当执行每条SQL语句时,ORACLE在内部执行了许多工作: 

解析SQL语句、估算索引的利用率、绑定变量、读数据块等等。 

由此可见,减少访问数据库的次数,就能实际上减少ORACLE的工作量。 

 

举例: 

题目——我要查找编号为0001、0002学生的信息。 

(低效) 

select name,age,gender,address from t_student where id = '0001'; 

select name,age,gender,address from t_student where id = '0002'; 

(高效) 

select a.name,a.age,a.gender,a.address,b.name,b.age,b.gender,b.address from t_student a,t_student b where a.id = '0001' and b.id = '0002';

 

10、使用Decode函数来减少处理时间: 

使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表。 

 

举例: 

(低效) 

select count(*), sum(banace) from table1 where dept_id = '0001' and name like 'anger%'; 

select count(*), sum(banace) from table1 where dept_id = '0002' and name like 'anger%'; 

(高效) 

select count(decode(dept_id,'0001','XYZ',null)) count_01,count(decode(dept_id,'0002','XYZ',null)) count_02, 

sum(decode(dept_id,'0001',dept_id,null)) sum_01,sum(decode(dept_id,'0002',dept_id,null)) sum_02 

from table1 

where name like 'anger%';

 

 

 

11、整合简单,无关联的数据库访问: 

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

 

举例: 

(低效) 

select name from table1 where id = '0001'; 

select name from table2 where id = '0001'; 

select name from table3 where id = '0001'; 

(高效) select t1.name, t2.name, t3.name 

    from table1 t1, table2 t2, table3 t3 

    where t1.id(+) = '0001' and t2.id(+) = '0001' and t3.id(+) = '0001' 

 

注:上面例子虽然高效,但是可读性差,需要量情而定啊!

 

12、删除重复记录: 

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

 

举例: 

delete from table1 t1 

where t1.rowid > (select min(t2.rowid) from table1 t2 where t1.id = t2.id); 

 

13、尽量不要使用having子句,可以考虑用where替换: 

having只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作。 

如果能通过where子句限制记录的数目,那就能减少这方面的开销。 

 

14、尽量用表的别名: 

当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上。 

这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。 

15、用exists替换distinct: 

当提交一个包含一对多表信息的查询时,避免在select子句中使用distinct. 一般可以考虑用exists替换 

 

举例: 

(低效) 

select distinct d.dept_no, d.dept_name from t_dept d, t_emp e where d.dept_no = e.dept_no; 

(高效) 

select d.dept_no, d.dept_name from t_dept d where exists (select 1 from t_emp where d.dept_no = e.dept_no); 

 

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

16、用表连接替换exists: 

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

 

举例: 

(低效) 

select ename from emp e where exists (select 1 from dept where dept_no = e.dept_no and dept_cat = 'W'); 

SELECT ENAME 

(高效) 

select ename from dept d, emp e where e.dept_no = d.dept_no and dept_cat = 'W'; 

转载于:https://my.oschina.net/fuweiwei/blog/178276

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值