1. 程式人生 > >SQL語句級別的優化總結

SQL語句級別的優化總結

第一部分:sql級別的優化:
1、 SQL語句儘量都大寫字母出現。
2、 查詢時,如果基表(from最後面的表),資料庫語句處理from後面的語句時,是從右側往
左側處理的,那麼選擇資料量最小的表作為基表,可加快查詢速度,同時,如果三張表,中間的那個交叉表,作為基表。
例如:A,1萬條, B,10萬條, C,100萬條資料。 
關聯條件: A.id=B.id and C.sid=B.sid此時,B就是交叉表。
 查詢時: select * from C,A,B where A.id=B.id and C.sid=B.sid (此時,B作為基表出現。)


3、where後面的條件順序: 連線條件放前面, 而其他的條件放後面,由於sql從右側往左側執行,此時可以過濾掉大部分資料,

   較少不必要的連線次數。
4、sql語句,儘量較少對資料庫表的訪問次數。
  這裡介紹兩種:
 (1)子查詢時,沒用到組函式的情況下,可以使用外連線做,這樣可以較少對錶訪問次數。
例如:查詢跟SMITH同一部門的員工資訊:
        select * from emp where deptno=(select deptno from emp where ename='SMITH') and ename!='SMITH'
(效率低下,訪問多次表)


select d.* from emp e left join emp d on e.deptno=d.deptno where e.ename='SMITH'  and d.ename!='SMITH'



select d.* from emp e , emp d where e.deptno=d.deptno and e.ename='SMITH'  and d.ename!='SMITH'


  (2) 一般用於統計時, 用到了組函式,每次統計資料時都分了很多情況,此時可考慮用
     case when then end來做。

    例如: 成績表:score
         編號      學號    成績  課程編號   
         coreNum    stno   scoreNum   clno
create table score(corenum number(5), stno number(5),scorenum number(5),clno number(5))

   查詢及格率以及不及格率:


下面查詢了一個不及格率:效率低下。 
select a1/a2 from (select count(*)  a1 from score where scoreNum<60 and clno=111) B1, (select count(distinct stno)  a2 from score where clno=111) B2


下面效率高: (如果是每個課程的,那麼直接加group by clno即可)
select round(sum(case when scorenum <60 then 1 else 0 end)/count(distinct stno),2), round(sum(case when scorenum >=60 then 1 else 0 end)/count(distinct stno),2 )  from score where  clno=111






insert into score values(1,1001,40,111);
insert into score values(2,1001,60,112);
insert into score values(3,1001,80,113);


insert into score values(4,1002,100,111);
insert into score values(5,1002,40,112);
insert into score values(6,1002,80,113);


insert into score values(7,1003,50,111);
insert into score values(8,1003,40,112);
insert into score values(9,1003,66,113);


insert into score values(10,1004,51,111);
insert into score values(11,1004,43,112);
insert into score values(12,1004,69,113);


insert into score values(13,1005,51,111);
insert into score values(14,1005,43,112);
insert into score values(15,1005,69,113);


insert into score values(16,1006,54,111);
insert into score values(17,1006,90,112);
insert into score values(18,1006,69,113);






(3) 如果是等值的多條件,選擇,可以通過case when等效的函式,decode來做。
例如:
decode用法為: select decode(to_char(comm),null,'no comm',0 ,'為0',10) from emp;


(4)使用where子句替換having
 例如:這裡可以用having,但是效率低,要改為where
select empno,sal from emp having  sal> (select avg(sal) from emp ) group by sal,empno


例如:select empno,sal from emp having  sal>300 group by sal,empno  查詢員工工資大於300的。 這樣寫跟where查詢一樣結果,但是效率低下。 
     select empno,sal from emp where  sal>300


(5)適當的使用內部函式(PLSQL中的自定義函式) ,以及儲存過程(資料庫端的業務邏輯比較複雜的)。




(6)適當使用表的別名或者列別名。
(7)用exists替代 in或者not exists 替代 not in:
例如:查詢跟SMITH同部門的員工資訊。
select * from emp e where e.deptno in( select  d.deptno from emp d where d.ename='SMITH') 最底下。


select * from emp e where exists(select 'x' from emp d where d.ename='SMITH' and  e.deptno=d.deptno )  (最好)


select e.* from emp e left join emp d on  e.deptno=d.deptno where d.ename='SMITH' (較好)




(8) 使用索引: 
  經常被訪問的列,並且這個列是非主鍵(或者外來鍵),查詢列資料範圍跨度比較大,在行記錄中出現次數比率低,這行記錄中空值多。不是每個列都要加索引。


  跟索引有關的列的操作:
     (8.1) 使用索引的列儘量不要使用not(條件),也儘量不要使用is null或者 is  not null。
原因:使用not後,索引失效,資料庫會將這列資料全掃描。




     (8.2) 使用索引的列儘量不要使用計算操作。 例如:
      select * from emp where sal*100>3000;(效率低)
      select * from emp where sal>3000/100;(效率高)




     (8.3)針對多個索引列出現在where子句的or條件下時:使用union比or效率高:(使用or時,這些列忽略了索引)
例如:假設emp表中sal和ename都使用了索引,並且語句中sal和ename都是or的條件。考慮用union不用or:
      select * from emp where sal>3000 or ename like '%張%' (效率低下)
      select * from  emp where sal >3000 union select * from emp where ename like '%張%'(效率高)




       (8.4) 一個索引,加在一個表中多列上, 此時如果where子句出現了這幾列,此時起作用的索引列是靠前的列。
     例如: create index b on(emp.ename,emp.sal,emp.comm)
     select * from emp where  e.sal >3000 and e.comm >200 and e.ename like '%S%' (此時,索引對ename起作用,對其他兩      列失效-這裡我指的是全盤掃描資料)。
       (8.5) order by後面使用索引列時,不要使用約束為null的列,這列索引失效。 
       (8.6) 在對索引列使用to_char 或者to_number函式時, 此時索引失效。 
例如:select * from emp where empno='10001' 這種隱士轉化,對索引無影響。
     select * from emp where empno=to_number('10001') 對非索引列使用轉換函式,對索引無影響。
              select * from emp where to_char(empno)='10001' 此時,如果該empno是索引列,該索引失效。






(9)distinct 去重複效率低下: 可以通過exists實現:
  select distinct d.dname from dept d,emp e where d.deptno=e.deptno (效率低下)


  select d.dname from dept d where exists(select 'x' from emp e where e.deptno=d.deptno)  (效率高)


(10)從java角度出發:避免過多的使用string欄位來 “+”號連線,拼接sql語句。
     可以考慮通過StringBuffer 中append方法追加sql。


(11) 使用>= 替代>
     例如: select * from emp where deptno>=10(效率高,因為depto直接定位到10,效率高)
            select * from emp where deptno>9(先定位到9 ,然後還要排除9 ,效率低)


(12) 如果使用union或者union all了,此時如果不需要考慮去掉重複的資料,儘量不要使用union ,因為union預設是去重複的
  在去重複過程中,也會影響效率。


(13) 對group by子句的優化上面:
       儘量在group by前面將資料過濾掉。
    例如:
   select job,avg(sal) from emp where job='CLERK' group by job (效率高)


   select job,avg(sal) from emp group by job having job='CLERK' (效率低)


(14)使用檢視: (資料量非常大的情況下)
     經常被查詢的列資料,並且這些資料不被經常的修改,刪除。 




第二部分:


Oracle面試題:
  (1)行變列: case when then end;
       empno     ename  hiredate
7499ALLEN1981/2/20
7521WARD1981/2/22
7566JONES1981/4/2
7654MARTIN1981/9/28
7698BLAKE1981/5/1
7782CLARK1981/6/9
7788SCOTT1987/4/19
7839KING1981/11/17
7844TURNER1981/9/8




  查詢結果要求:
  empno  ename   1981/1/1-1981/12/1    1982/1/1-1982/12/1     1987/1/1-1987/12/1
  7499ALLEN   1981/2/20
  7498SMITH                        1982/4/20
  7844TURNER   1981/9/8
  7788SCOTT                                               1987/4/19



查詢語句:
select empno,ename ,case when hiredate between '1-1月-1980' and '1-12月-1980'  then hiredate else null end "1980/1/1-1980/12/1",
case when hiredate between '1-1月-1981' and '1-12月-1981'  then hiredate else null end "1981/1/1-1981/12/1",
  case when hiredate between '1-1月-1982' and '1-12月-1982'  then hiredate else null end  "1982/1/1-1982/12/1",
    case when hiredate between '1-1月-1983' and '1-12月-1983'  then hiredate else null end  "1983/1/1-1983/12/1",
      case when hiredate between '1-1月-1984' and '1-12月-1984'  then hiredate else null end  "1984/1/1-1984/12/1",
        case when hiredate between '1-1月-1985' and '1-12月-1985'  then hiredate else null end  "1985/1/1-1985/12/1",
         case when hiredate between '1-1月-1986' and '1-12月-1986'  then hiredate else null end  "1986/1/1-1986/12/1",
        case when hiredate between '1-1月-1987' and '1-12月-1987'  then hiredate else null end  "1987/1/1-1987/12/1"


from emp




(2) 刪除重複行:(效率最好的) 
 delete from emp e where e.rowid>(
 select min(d.rowid) from emp d where e.empno=d.empno
)


問:delete 和trunc區別?
trunc是截斷表,將表所有資料刪除,釋放表空間。
不可以回滾。
 delete:刪除表資料,不釋放表空間,可以回滾。






(3) 刪除所有表中資料:(truncate-截斷表比delete效率高)
  truncate的特點是,釋放表空間,事務不能回滾。


(4)資料庫表語句的型別有哪些:
  DML:資料庫操縱語言: 主要對資料庫資料進行操作的。delete,update,select等。
  DCL:資料庫控制語言:主要有:commit,rollback,授權,使用者等操作。
  DDL:資料庫定義語言:主要有:drop,alter (對錶結構操作的 )。






(5)資料庫的三正規化:
  第一正規化:資料庫表中列,拆到不能拆為止。

   例如: 此時,電話列,存放固定電話號碼也存行動電話,此時這一列使整體資料有冗餘。
     編號 姓名    電話    
     1001  zyg    15845689182  
     1001  zyg    010-67676767    
   修改方法:
    編號 姓名    固定電話               行動電話
    1001  zyg    010-67676767           15845689182




  第二正規化:(滿足第一正規化前提下) 較少非主鍵列之間的依賴關係,適當的使用外來鍵(拆成多個表)

    例如: 學生資訊表以及成績,課程都設計在一個表中。(有冗餘資料)
     學號  姓名    課程   成績
      1001  zyg    1       78
      1001  zyg    2       90


    修改為:  成績表:
     學生表:            課程表:                  序號 成績,課程編號,學號
      學號  姓名   課程   課程名稱          1 89      1       1001
      1001  zyg   1       css              290      2       1001
        2        HTML







例如: 下面滿足一正規化,二正規化,不滿足三正規化:


      學生表:            課程表:                  序號 成績,課程編號,學號
      學號  姓名  學院 學院名稱 課程   課程名稱          1 89      1       1001
      1001  zyg   1aaa 1       css               290      2       1001

      學院資訊表:
      學院編號   學院名稱



  第三正規化:在滿足一正規化,二正規化基礎上,在表中不能含有另一張表的非主鍵列。
例如上面學生表中,出現的學院名稱,在學員資訊表中也存在,這是不滿足第三正規化的。