SQL語句級別的優化總結
阿新 • • 發佈:2018-12-30
第一部分: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
學院資訊表:
學院編號 學院名稱
第三正規化:在滿足一正規化,二正規化基礎上,在表中不能含有另一張表的非主鍵列。
例如上面學生表中,出現的學院名稱,在學員資訊表中也存在,這是不滿足第三正規化的。
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
學院資訊表:
學院編號 學院名稱
第三正規化:在滿足一正規化,二正規化基礎上,在表中不能含有另一張表的非主鍵列。
例如上面學生表中,出現的學院名稱,在學員資訊表中也存在,這是不滿足第三正規化的。