Oracle查询优化-03操作多个表
- 1 記錄集的疊加
- 問題
- 解決方案
- 結論
- 2 組合相關的行
- 問題
- 解決方案
- 結論
- 4 INEXISTS 和 INNER JOIN
- 問題
- 解決方案
- IN
- EXISTS
- INNER JOIN
- 結論
- 5 INNER JOINLEFT JOINRIGHT JOIN 和 FULL JOIN 解析
- 問題
- 解決方案
- inner join的特點
- left join的特點
- right join的特點
- full join的特點
- 6 自關聯
- 問題
- 解決方案
- 結論
- 7 NOT INNOT EXISTS 和 LEFT JOIN
- 問題
- 解決方案
- not in
- not exists
- left join
- 結論
- 8 外連接中的條件不要亂放
- 問題
- 解決方案
- 9 檢測兩個表中的數據及對應數據的條數是否相同
- 問題
- 解決方案
- 10 聚集與內連接
- 問題
- 解決方案
- 結論
- 11 聚集與外連接
- 問題
- 解決方案
- 結論
- 12 從多個表中返回丟失的數據
- 問題
- 解決方案
- full join
- union all
- 13 多表查詢時的空值處理
- 問題
- 解決方案
3.1 記錄集的疊加
問題
要將來自多個表的數據組織到一起,就像將一個結果集疊加到另外一個上面一樣。 這些表不必有相同的關鍵字,但是他們對應列的數據類型必須相同。
解決方案
使用union all 把多個表中的行組合到一起。
select ename, deptnofrom empwhere deptno = 10 union all select '-----', deptnofrom dept union all select dname, deptno from dept;結論
UNION ALL將多個來源的行組合起來,放到一個結果集中。 所有select列表中的項目數和對應項目的數據類型必須要匹配。
UNION ALL會包括重復的項目,如果要篩選掉重復項,可以使用UNION運算符。
如果使用UNION而不是UNION ALL,很可能是為了去除重復項而進行排序操作。 在處理大結果集時要記住,使用UNION子句大致相當于下面的查詢,對UNION ALL子句的查詢結果使用DISTINCT子句
3.2 組合相關的行
問題
多表有一些相同的列,或者有些列的值相同,需要通過關聯這些列得到結果。
解決方案
select a.ename ,b.dname from emp a ,dept b where a.deptno = b.deptno and a.deptno = 10 ;select a.ename ,b.dname from emp a inner join dept b on a.deptno = b.deptno where a.deptno = 10 ;結論
第二種解決方式是利用顯示的JOIN子句(inner 關鍵字可省略),如果希望將聯接邏輯關系放在from子句中,而不是在where 子句中,可以使用JOIN子句, 這兩種方式都符合ANSI標準。
3.4 IN、EXISTS 和 INNER JOIN
問題
先創建一個表EMP2
create table emp2 asselect ename, job, sal, comm from emp where job = 'CLERK';要求返回與emp2(ename, job, sal)中數據相匹配的emp(ename, job, sal,deptno)信息
有in , exists 和 inner join 三種寫法,為了加強理解,我們來看下三種寫法及其對應的執行計劃。
解決方案
ORACLE VERSION : Oracle Database 11g Enterprise Edition Release 11.2.0.4.0
IN
SQL> explain plan for 2 select ename, job, sal, deptno 3 from emp 4 where (ename, job, sal) in (select ename, job, sal from emp2);ExplainedSQL> select * from table(dbms_xplan.display());PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 4039873364 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 4 | 260 | 6 (0)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 4 | 260 | 6 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP | 14 | 546 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP2 | 4 | 104 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ENAME"="ENAME" AND "JOB"="JOB" AND "SAL"="SAL") Note ----- - dynamic sampling used for this statement (level=2)19 rows selectedSQL>EXISTS
SQL> explain plan for 2 select ename, job, sal, deptno 3 from emp a where exists (select * from emp2 b 4 where b.ename= a.ename 5 and b.job = a.job 6 and b.sal = a.sal) ;ExplainedSQL> select * from table(dbms_xplan.display());PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 4039873364 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 4 | 260 | 6 (0)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 4 | 260 | 6 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP | 14 | 546 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP2 | 4 | 104 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("B"."ENAME"="A"."ENAME" AND "B"."JOB"="A"."JOB" AND "B"."SAL"="A"."SAL") Note ----- - dynamic sampling used for this statement (level=2)20 rows selectedSQL>INNER JOIN
因為子查詢的join列(emp2.ename ,emp2.job ,emp2.sal)沒有重復行,說這個查詢可以直接改寫為inner join
SQL> explain plan for 2 select a.ename, a.job, a.sal, a.deptno 3 from emp a join emp2 b on 4 (a.ename = b.ename 5 and a.job= b.job 6 and a.sal =b.sal);ExplainedSQL> select * from table(dbms_xplan.display());PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 166525280 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 4 | 260 | 6 (0)| 00:00:01 | |* 1 | HASH JOIN | | 4 | 260 | 6 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP2 | 4 | 104 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP | 14 | 546 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("A"."ENAME"="B"."ENAME" AND "A"."JOB"="B"."JOB" AND "A"."SAL"="B"."SAL") Note ----- - dynamic sampling used for this statement (level=2)20 rows selectedSQL>結論
或許與大家想象的不一樣,以上三個PLAN中join寫法利用了hash join(哈希連接),其他兩種運用的是 hash join semi(哈希半連接) 。 說明在這個語句中 in 和 exists的效率是一樣的。
所以,在不知道哪種寫法高效時應該查看Plan,而不是去記固定的結論。
3.5 INNER JOIN、LEFT JOIN、RIGHT JOIN 和 FULL JOIN 解析
問題
有人對這幾種連接方式,特別是left join 和 right join 分不清楚,下面通過案例來分析一下。
解決方案
SQL>CREATE TABLE L AS SELECT 'left_1' AS str,'1' AS v FROM dual UNION ALL SELECT 'left_2','2' AS v FROM dual UNION ALL SELECT 'left_3','3' AS v FROM dual UNION ALL SELECT 'left_4','4' AS v FROM dual;/*右表*/ SQL>CREATE TABLE R AS SELECT 'right_3' AS str,'3' AS v,1 AS status FROM dual UNION ALL SELECT 'right_4' AS str,'4' AS v,0 AS status FROM dual UNION ALL SELECT 'right_5' AS str,'5' AS v,0 AS status FROM dual UNION ALL SELECT 'right_6' AS str,'6' AS v,0 AS status FROM dual; SQL> select * from l ;STR V ------ - left_1 1 left_2 2 left_3 3 left_4 4SQL> select * from r;STR V STATUS ------- - ---------- right_3 3 1 right_4 4 0 right_5 5 0 right_6 6 0SQL>inner join的特點
該方式返回兩表相匹配的數據。
inner join寫法: select l.str, r.str from l inner join r on l.v = r.v order by 1, 2;where寫法: select l.str, r.str from l, r where l.v = r.v order by 1, 2;輸出: STR STR ------ ------- left_3 right_3 left_4 right_4left join的特點
該方式以左表為主表,左表返回所有的數據,右表只返回與左表匹配的數據。
SQL> select l.str, r.str from l left join r on l.v = r.v order by 1, 2;STR STR ------ ------- left_1 left_2 left_3 right_3 left_4 right_4SQL>(+)寫法:
SQL> select l.str, r.str from l, r where l.v = r.v(+) order by 1, 2;STR STR ------ ------- left_1 left_2 left_3 right_3 left_4 right_4SQL>right join的特點
該方式以右表為主表,右表返回所有的數據,左表只返回與左表匹配的數據。
select l.str, r.str from l right join r on l.v = r.v order by 1, 2;(+)寫法: select l.str, r.str from l, r where l.v(+) = r.v order by 1, 2;SQL> select l.str, r.str from l right join r on l.v = r.v order by 1, 2;STR STR ------ ------- left_3 right_3 left_4 right_4right_5right_6SQL> select l.str, r.str from l, r where l.v(+) = r.v order by 1, 2;STR STR ------ ------- left_3 right_3 left_4 right_4right_5right_6SQL>full join的特點
該方式的左表和右表都返回所有的數據,但只有相匹配的數據顯示在同一行,非匹配的行只顯示一個表的數據。
SQL> select l.str, r.str from l full join r on l.v = r.v order by 1, 2;STR STR ------ ------- left_1 left_2 left_3 right_3 left_4 right_4right_5right_66 rows selectedSQL>注意 FULL JOIN沒有(+)寫法
3.6 自關聯
問題
表emp中有個字段 mgr,是主管的編碼(對應于emp.empno)
如何根據這個信息返回主管的姓名呢?
解決方案
自關聯,也就是兩次查詢表emp,分別取不同的別名,這樣就可以當做是兩個 表,后面的任務就是將這兩個表 join連接起來即可。
為了便于理解,這里我們使用漢字作為別名,并把相關列一起返回。
select 員工.empno as 員工編碼 , 員工.ename as 員工name, 員工.job as 工作, 員工.mgr as 員工表主管編碼, 主管.empno as 主管表主管編碼, 主管.ename as 主管姓名 from emp 員工 left join emp 主管 on (員工.mgr = 主管.empno) order by 1;結論
上述的操作,可以理解為我們是在兩個不同的數據集中取數據。
create or replace view 員工 as select * from emp ; create or replace view 主管 as select * from emp ; select 員工.empno as 員工編碼 , 員工.ename as 員工name, 員工.job as 工作, 員工.mgr as 員工表主管編碼, 主管.empno as 主管表主管編碼, 主管.ename as 主管姓名 from 員工 left join 主管 on (員工.mgr = 主管.empno) order by 1;3.7 NOT IN、NOT EXISTS 和 LEFT JOIN
問題
有些單位的部門如40中一個員工也沒有,只是設置了一個部門名字,如何通過關聯查詢把這些信息查詢出來呢?
解決方案
數據庫版本 11.2.0.4.0
alter table dept add constraints pk_dept primary key(deptno)執行以下SQL并查詢執行計劃
not in
explain plan for select * from dept where deptno not in (select deptno from emp where deptno is not null);not exists
explain plan for select * from dept where not exists (select null from emp where emp.deptno = dept.deptno)left join
Left join 取出的是左表中所有的數據,其中右表不匹配的就表示左表not in 右表
explain plan for select dept.* from dept left join emp on emp.deptno = dept.deptno where emp.deptno is null ;select * from table(dbms_xplan.display());結論
三個PLAN應用的都是 MERGE JOINANTI,說明這三種方法的效率是一樣的。
若果想改寫,那么就要比對前后的PLAN,根據PLAN來判斷并測試哪種方法的效率高,而不是憑借某些結論來碰運氣。
3.8 外連接中的條件不要亂放
問題
對于左連語句,見下面的數據
SQL> select l.str, r.str ,r.status from l left join r on l.v = r.v order by 1, 2;STR STR STATUS ------ ------- ------- left_1 left_2 left_3 right_3 1 left_4 right_4 0SQL>對于L表,4條數據全部返回了,而對于R表,我們如果 只需要顯示 status=1的部分,該如何寫SQL呢?
常見的錯誤寫法, 會有人直接在上面的語句中加入條件 status=1
select l.str, r.str ,r.status from l left join r on l.v = r.v where r.status=1order by 1, 2;我們來看下返回結果:
STR STR STATUS ------ ------- ------- left_3 right_3 1很明顯這不是我們想要的數據集。這是很多人寫查詢或者改查詢時常遇到的一種錯誤, 問題在于所加條件的位置及寫法。
那該如何做呢?
解決方案
left join寫法
select l.str, r.str, r.statusfrom lleft join ron (l.v = r.v and r.status = 1)order by 1, 2;(+)寫法
select l.str, r.str, r.statusfrom l, rwhere l.v = r.v(+)and r.status(+) = 1order by 1, 2;3.9 檢測兩個表中的數據及對應數據的條數是否相同
問題
查找視圖V 和 emp表中不同的數據
我們先創建一個視圖
create or replace view v as select * from emp where deptno !=10union all select * from emp where ename='SCOTT';我們可以知道 視圖V中,SCOTT有兩條記錄, EMP中有一條
SQL> select * from v where ename='SCOTT';EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ----- ---------- --------- ----- ----------- --------- --------- ------7788 SCOTT ANALYST 7566 1987-04-19 3000.00 207788 SCOTT ANALYST 7566 1987-04-19 3000.00 20SQL> select * from emp where ename='SCOTT';EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ----- ---------- --------- ----- ----------- --------- --------- ------7788 SCOTT ANALYST 7566 1987-04-19 3000.00 20SQL>比較兩個數據集的不同時,通常類似下面的FULL JOIN 語句。
SQL> select v.EMPNO, v.ENAME, emp.empno, emp.ename2 from v3 full join emp4 on v.EMPNO = emp.empno5 where (v.EMPNO is null or emp.empno is null);EMPNO ENAME EMPNO ENAME ----- ---------- ----- ----------7782 CLARK7839 KING7934 MILLERSQL>但是這種語句查不到 SCOTT的區別 。
解決方案
增加一列顯示相同數據的條數,再進行比較。
select v_new.empno,v_new.ENAME,v_new.cnt,emp_new.empno,emp_new.ename,emp_new.cntfrom (select v.empno, v.ENAME, count(1) as cntfrom vgroup by v.empno, v.ENAME) v_newfull join (select a.empno, a.ENAME, count(1) as cntfrom emp agroup by a.empno, a.ENAME) emp_newon (v_new.EMPNO = emp_new.empno and v_new.cnt = emp_new.cnt)where (v_new.EMPNO is null or emp_new.empno is null);結果集:
EMPNO ENAME CNT EMPNO ENAME CNT ----- ---------- ---------- ----- ---------- ----------7788 SCOTT 17934 MILLER 17782 CLARK 17839 KING 17788 SCOTT 2 SQL>3.10 聚集與內連接
問題
解決方案
結論
3.11 聚集與外連接
問題
解決方案
結論
3.12 從多個表中返回丟失的數據
問題
同時返回多個表中丟失的數據。 要從DEPT中返回EMP不存在的行(所有沒有員工的部門)需要做外連接。
首先我們在EMP中增加一行deptno為空的數據,如下:
insert into emp(empno, ename, job, mgr, hiredate, sal, comm, deptno)select 6666, 'XGJ', 'JEDI', null, hiredate, sal, comm, nullfrom empwhere ename = 'KING';commit ;此時,我們來看下 我們要查詢的表中數據:
這時,如果我們使用下面的語句關聯查詢 ,就會發現少了emp=6666和 deptno=40的數據
select e.empno, e.ename, b.deptno, b.dnamefrom emp ejoin dept bon e.deptno = b.deptno;如果想要返回這兩條數據該如何寫查詢語句呢? 下面介紹兩種方法
解決方案
full join
select e.empno, e.ename, b.deptno, b.dnamefrom emp efull join dept bon e.deptno = b.deptno;union all
select e.empno, e.ename, b.deptno, b.dnamefrom emp eleft join dept bon e.deptno = b.deptnounion allselect e.empno, e.ename, b.deptno, b.dnamefrom emp eright join dept bon e.deptno = b.deptno where e.empno is null ;在這里不建議使用union ,因為union會去掉重復記錄。 如果確定需要去掉重復記錄再使用。
3.13 多表查詢時的空值處理
問題
NULL值永遠不會等于或者不等于任何值,也包括null自己,但是需要像計算真實值一樣計算可為空列的返回值。
返回所有比ALLEN提成低的員工, 提成 comm字段 ,有空值 。
數據如下:
SQL> select * from emp;EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ----- ---------- --------- ----- ----------- --------- --------- ------7369 SMITH CLERK 7902 1980-12-17 800.00 207499 ALLEN SALESMAN 7698 1981-02-20 1600.00 300.00 307521 WARD SALESMAN 7698 1981-02-22 1250.00 500.00 307566 JONES MANAGER 7839 1981-04-02 2975.00 207654 MARTIN SALESMAN 7698 1981-09-28 1250.00 1400.00 307698 BLAKE MANAGER 7839 1981-05-01 2850.00 307782 CLARK MANAGER 7839 1981-06-09 2450.00 107788 SCOTT ANALYST 7566 1987-04-19 3000.00 207839 KING PRESIDENT 1981-11-17 5000.00 107844 TURNER SALESMAN 7698 1981-09-08 1500.00 0.00 307876 ADAMS CLERK 7788 1987-05-23 1100.00 207900 JAMES CLERK 7698 1981-12-03 950.00 307902 FORD ANALYST 7566 1981-12-03 3000.00 207934 MILLER CLERK 7782 1982-01-23 1300.00 1014 rows selectedSQL>我們來看個錯誤的寫法:
SQL> select a.ename , a.comm from emp a where a.comm < (select comm from emp a where a.ename = 'ALLEN');ENAME COMM ---------- --------- TURNER 0.00SQL>只返回了一條 TURNER的數據,comm有許多空值 的并沒有被顯示出來,原因在于與空值比較后結果還是空值,需要先轉換才行
解決方案
使用coalesce函數將null值轉換為一個可以用來作為標準值進行比較的真實值。
coalesce函數從值列表中返回第一個非NULL值。當遇到NULL值將其替換為0,這樣就可以同ALLEN的提成進行比較了。
SQL> select a.ename , a.comm from emp a where coalesce( a.comm,0 ) < (select comm from emp a where a.ename = 'ALLEN');ENAME COMM ---------- --------- SMITH JONES BLAKE CLARK SCOTT KING TURNER 0.00 ADAMS JAMES FORD MILLER 11 rows selectedSQL>或者
select a.ename , a.comm from emp a where nvl( a.comm,0 ) < (select comm from emp a where a.ename = 'ALLEN');總結
以上是生活随笔為你收集整理的Oracle查询优化-03操作多个表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle查询优化-02给查询结果排序
- 下一篇: Notice 博客暂时停更