七、多表查詢 對於查詢在之前已經學過了簡單查詢、限定查詢、查詢排序,這些都屬於 SQL 的標準語句,而上一章的單行函數,主要功能是為了彌補查詢的不足。 而從多表查詢開始就正式進入到了複雜查詢部分。 7.1、基本語法 多表查詢就是在一條查詢語句中,從多張表裡一起取出所需要的數據。如果要想進行多 ...
七、多表查詢
對於查詢在之前已經學過了簡單查詢、限定查詢、查詢排序,這些都屬於 SQL 的標準語句,而上一章的單行函數,主要功能是為了彌補查詢的不足。
而從多表查詢開始就正式進入到了複雜查詢部分。
7.1、基本語法
多表查詢就是在一條查詢語句中,從多張表裡一起取出所需要的數據。如果要想進行多表查詢,直接在 FROM 子句之後跟上多個表即可,語法如下:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],... FROM 表名稱1[表別名1],表名稱2[表別名2],... [WHERE 條件(s)] [ORDER BY 排序的欄位1 ASC|DESC,排序的欄位2 ASC|DESC,...];
下麵就將採用 emp 表和 dept 表一起進行多表查詢,查詢之前多做一個步驟,先確定 emp 和 dept 表中的數據量分別有多少,可以使用 COUNT() 函數統計
範例:統計 emp 表中的數據量 (14 行記錄)
SELECT COUNT(*)
FROM emp;
範例:統計 dept 表中的數據量 (4行記錄)
SELECT COUNT(*)
FROM dept;
範例:現在查詢所有的雇員和部門的全部詳細信息
SELECT COUNT(*)
FROM emp,dept;
- 發現此時結果一共返回了56行記錄,這就是笛卡爾積 造成的問題。現在就發現了,笛卡爾積的出現,可以讓查詢結果變得非常的龐大,如果現在兩張表的數據量都很大,那麼這種龐大是很可怕的,所以現在必須想辦法消除掉笛卡爾積。
- 一般而言,如果要想進行笛卡爾積的消除,往往會使用關聯欄位,由於多張表之間可能會存在重名的欄位,所以進行重名欄位的訪問的時候看,前面需要加上表名稱,採用 “ 表名稱 欄位 ” 的方式來進行訪問。
範例:利用等值條件來處理笛卡爾積
SELECT *
FROM emp,dept
WHERE emp.deptno = dept.deptno;
- 這時的結果就可以發現已經消除掉笛卡爾積,但是這時積依然存在,只是不顯示了而已
- 已經清楚基本概念後,下麵就可以針對於數據量做一個分析。在 Oracle 中存在了一個 sh 用戶,當然此用戶保存在了 pdbmldn 插入式資料庫之中了
- 1、打開 sqlplus :運行 - 輸入 sqlplus /nolog
- 2、管理員連接資料庫:conn sys/change_on_install AS SYSDBA;
- 3、切換到 pdbmldn 資料庫:ALTER SESSION SET CONTAINER=pdbmldn;
- 4、打開 pdbmldn 資料庫:ALTER DATABASE pdbmldn OPEN
- 馬上就要使用 sh 用戶進行操作,現在可以使用 sh 用戶下的 sales 和 costs 表,這兩張表的數量都比較大。
範例:查看 sh.sales 表的數據量 (918843條記錄)
SELECT COUNT(*)
FROM sh.sales;
範例:查看 sh.costs 表的數據量 (82112條記錄)
SELECT COUNT(*)
FROM sh.costs;
範例:如果現在直接將這兩張表進行多表查詢,那麼來觀察問題
SELECT COUNT(*)
FROM sh.sales,sh.costs;
- 一旦開始執行之後,那麼這個等待的過程會很長,但是這時是顯示所有數據量(包含笛卡爾積的數據量),而後再開始消除,利用等值關聯。最終會返回的數據量:75,448,036,416
- 雖然消除掉了所有顯示的笛卡爾積,但是資料庫的原理機制及表示笛卡爾積會永遠存在
- 多表查詢會產生笛卡爾積,所以性能較差,可以利用等值關聯欄位消除笛卡爾積
7.2、多表查詢實例
- 雖然多表查詢本身存在了性能問題,但並不表示多表查詢無法使用,需要一些更合理的做法來解決多表查詢問題
範例:查詢每個雇員的編號、姓名、職位、基本工資、部門名稱、部門位置信息
- 確定所需要的數據表
- emp 表:查詢每個雇員的編號、姓名、職位、基本工資;
- dept 表:部門名稱,部門位置。
- 確定已知的關聯欄位
- 部門與雇員關聯:emp.deptno = dept.deptno
- 隨後還需要按照一個 SQL 語句的執行步驟編寫:FROM , WHERE , SELECT 。
SELECT emp.empno,emp.ename ,emp.job,emp.sal,dept.dname,dept.loc
FROM emp,dept
WHERE emp.deptno = dept.deptno
- 但是在此處就會存在一個問題,上面程式里都是採用了表名稱訪問的列名稱,如果現在表名稱很長:yuzhou_yinhexi_diqiu_yazhou_zhongguo_beijing 。所以往往在多表查詢的時候為查詢的數據定義別名,而別名也是在 FROM 子句之中定義的,上面的程式可以改寫為:
SELECT e.empno, e.ename, e.job, e.sal, d.dname, d.loc
FROM emp e, dept d
WHERE e.deptno=d.deptno;
- 在以後的程式編寫之中幾乎都會使用到別名
範例:查詢出每個雇員的編號、姓名、雇佣日期、基本工資、工資等級
- 確定所需要的數據表
- emp 表:查詢每個雇員的編號、姓名、雇佣日期、基本工資;
- salgrade 表:工資等級。
- 確定已知的關聯欄位
- 雇員和工資等級:emp.sal BETWEEN salgrade.local AND salgrade.hisal ;
SELECT e.empno, e.ename, e.hiredate, e.sal, s.grade
FROM emp e, salgrade s
WHERE e.sal BETWEEN s.losal AND s.hisal;
- 在之前的查詢裡面,發現只是顯示了數字1,2,3,4,5,現在希望可以將其替換為中文,如果要替換,肯定使用 DECODE() 函數。
範例:為了更加清楚的顯示出工資等級的信息,現在希望可以按如下格式進行替換顯示:
grade = 1 : 顯示為 “ E等工資 ”
grade = 2 : 顯示為 “ D等工資 ”
grade = 3 : 顯示為 “ C等工資 ”
grade = 4 : 顯示為 “ B等工資 ”
grade = 5 : 顯示為 “ A等工資 ”
SELECT e.empno, e.ename, e.hiredate, e.sal,
DECODE(s.grade,1,'E等工資',2,'D等工資',3,'C等工資',4,'B等工資',5,'A等工資') grade
FROM emp e, salgrade s
WHERE e.sal BETWEEN s.losal AND s.hisal;
- 前面講的都是針對於兩張表進行多表查詢,而且多表查詢裡面都只使用了一個條件來消除笛卡爾積;如果現在是多個消除笛卡爾積的條件,那麼往往使用 AND 將這些條件連接在一起
範例:查詢出每個雇員的姓名、職位、基本工資、部門名稱、工資等級
- 確定所需要的數據表
- emp 表:每個雇員的姓名、職位、基本工資;
- dept 表:部門名稱
- salgrade 表:工資等級。
- 確定已知的關聯欄位
- 雇員和部門:emp.deptno = dept.deptno
- 雇員和工資等級:emp.sal BETWEEN salgrade.local AND salgrade.hisal ;
SELECT e.ename, e.job, e.sal, d.dname,
DECODE(s.grade,1,'E等工資',2,'D等工資',3,'C等工資',4,'B等工資',5,'A等工資') grade
FROM emp e, dept d, salgrade s
WHERE e.deptno = d.deptno AND e.sal BETWEEN s.losal AND s.hisal;
- 多表查詢之中,每當增加一個關聯表都需要設置消除笛卡爾積的條件
7.3、表的連接操作
- 對於數據表的連接操作在資料庫中一共定義了兩種:
- 內連接:也稱為等值連接(或稱為連接,還可以被稱為普通連接或者自然連接),是最早的一種連接方式,內連接是從結果表中刪除與其他被連接表中沒有匹配行的所有元組,所以當匹配條件不滿足時內連接可能會丟失信息。在之前所使用的連接方式都屬於內連接,而在 WHERE 子句之中設置的消除笛卡爾積的條件就是採用了等值判斷的方式進行的。
- 外連接:內連接中只能夠顯示等值滿足的條件,如果不滿足的條件則無法顯示,如果現在希望特定表中的數據可以全部顯示,就利用外連接,外連接分為三種:左外連接(簡稱:左連接)、右外連接(簡稱:右連接)、全外連接(簡稱:全連接,在 SQL :1999 語法部分講解)
- 在之前所編寫的表連接操作都屬於內連接的定義範疇
- 為了更好的觀察各個連接方式的區別,首先需要在 emp 表中增加一條數據,這個增加語法會在後面解釋
INSERT INTO emp(empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES(8888,'李華華','CLERK',7369,SYSDATE,800,100,null);
- 這時增加完的數據是一個沒有部門的雇員,即此雇員的部門編號是 null ,增加完成之後查看一下 emp 表中當前的全部數據
SELECT * FROM emp;
- 此處沒有部門編號,下麵就演示等值連接所帶來的效果
範例:使用等值連接
SELECT *
FROM emp e, dept d
WHERE e.deptno = d.deptno;
- 通過此時的結果可以發現兩個問題:
- 問題一:沒有部門的雇員沒有顯示
- 問題二:有一個40部門沒有顯示
- 所以現在就可以發現,使用內連接只有滿足連接條件的數據才會全部顯示。可是如果說現在希望 emp 或是 dept 表中的數據顯示完整,就可以利用外連接進行。
- 外連接現在主要使用兩種:
- 左外連接:左關係屬性 = 右關係屬性(+) ,+ 放在了等號的右邊,表示左連接;
- 右外連接:左關係屬性(+) = 右關係屬性 ,+ 放在了等號的左邊,表示右連接。
範例:使用左外連接,顯示雇員編號是8888的信息
SELECT *
FROM emp e, dept d
WHERE e.deptno = d.deptno(+);
範例:使用右外連接,顯示部門編號為40的信息
SELECT *
FROM emp e, dept d
WHERE e.deptno(+) = d.deptno;
- 上面結果中因為40部門沒有雇員,所以所有的雇員信息都為 null 。
- 個人總結:使用外連接的環境,如果所需要的數據信息沒有顯示出來,那麼就使用外連接,而具體是左外還是右外,個人認為沒必要去記,去試就行!
7.4、自身關聯
- 在 emp 表中存在有一個 mgr 欄位,這個欄位表示的是雇員的領導
SELECT * FROM emp;
- 現在如果要顯示雇員的領導信息,那麼肯定利用雇員表和雇員表自己的連接操作完成。利用雇員表中的領導編號,找到對應此編號的雇員信息。
範例:查詢出每個雇員的編號、姓名及其上級領導的編號、姓名
- 確定所需要的數據表
- emp 表:雇員的編號、姓名
- emp 表:找到領導的編號、姓名
- 確定已知的關聯欄位
- 雇員和領導:emp.mgr = memp.empno (雇員的領導編號 = 領導的信息)
- 步驟一:直接進行自身連接的操作
SELECT e.empno eno, e.ename ename, m.empno mno, m.ename mname
FROM emp e, emp m
WHERE e.mgr = m.empno;
現在表中一共有15條記錄,但是只有14條的記錄顯示,等值連接在沒有條件滿足的時候,是不可能有數據顯示的。
在 emp 表中 king 這個雇員是沒有領導的,這個時候就必須考慮外連接。
- 步驟二:使用左外連接
SELECT e.empno eno, e.ename ename, m.empno mno, m.ename mname
FROM emp e, emp m
WHERE e.mgr = m.empno(+);
對於沒有領導信息的雇員,對應的領導信息,全部使用 null 進行表示
範例:查詢出在1981年雇員的全部雇員的編號、姓名、雇佣日期(按照年-月-日顯示)、工作、領導姓名、雇員月工資、雇員年工資(基本工資+獎金)、雇員工資等級、部門編號、部門名稱、部門位置、並且要求這些雇員的月基本工資在1500~3500之間,將最後的結果按照年工資的降序排列,如果年工資相等,則按工作進行排序
- 確定所需要的數據表
- emp 表:編號、姓名、雇佣日期、工作、月工資、計算年工資;
- emp 表:領導姓名;
- dept 表:部門編號、名稱、位置;
- salgrade 表:工資等級。
- 確定已知的關聯欄位
- 雇員和領導:emp.mgr = memp.empno;
- 雇員和部門:emp.deptno = dept.deptno;
- 雇員和工資等級:emp.sal BETWEEN salgrade.losal AND salgrade.hisal .
- 步驟一:查詢出所有在1981年雇佣的雇員編號、姓名、雇佣日期、工作、月工資、年工資、並且月薪在1500~3500之間。只需要 emp 單張表即可實現。
SELECT e.empno, e.ename, e.hiredate, e.sal, (e.sal+NVL(e.comm,0))*12 income
FROM emp e
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500;
- 步驟二:加入領導信息,使用自身關聯
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
m.ename mname
FROM emp e, emp m
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
AND e.mgr = m.empno(+);
- 步驟三:加入部門信息
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
m.ename mname, d.deptno dno, d.dname dname, d.loc
FROM emp e, emp m, dept d
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
AND e.mgr = m.empno(+)
AND e.deptno = d.deptno;
- 步驟四:查詢出工資等級
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade
FROM emp e, emp m, dept d,salgrade s
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
AND e.mgr = m.empno(+)
AND e.deptno = d.deptno
AND e.sal BETWEEN s.losal AND s.hisal;
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade,
DECODE(s.grade,1,'E等工資',2,'D等工資',3,'C等工資',4,'B等工資',5,'A等工資') 工資等級
FROM emp e, emp m, dept d,salgrade s
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
AND e.mgr = m.empno(+)
AND e.deptno = d.deptno
AND e.sal BETWEEN s.losal AND s.hisal;
- 步驟五:進行排序,由於 SELECT 是在 ORDER BY 子句之前執行,所以在 SELECT 子句之中所定義的別名,ORDER BY 子句是可以直接使用的。
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade,
DECODE(s.grade,1,'E等工資',2,'D等工資',3,'C等工資',4,'B等工資',5,'A等工資') 工資等級
FROM emp e, emp m, dept d,salgrade s
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
AND e.mgr = m.empno(+)
AND e.deptno = d.deptno
AND e.sal BETWEEN s.losal AND s.hisal;
ORDER BY income DESC, e.job;
- 通過這一稍微複雜點的題目,可以發現,所有的分析必須要分步進行,而且這些分析過程,需要大量的練習才可以鞏固。
- 自身關聯屬於一張表自己關聯自己的情況,此時依然會產生笛卡爾積。
7.5、SQL:1999 語法的支持
- 在之前使用的 “ (+) ” 標記只適合在 Oracle 資料庫之中應用,如果是其他的資料庫,是無法使用的
- SQL:1999 語法
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [CROSS JOIN 表2 表別名2]|
[NATURAL JOIN 表2 表別名2]|
[JOIN 表2 USING(關聯列名稱)]|
[JOIN 表2 ON(關聯條件)]|
[LEFT|RIGHT|FULL OUTER JOIN 表2 ON(關聯條件)]
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
- 實際上以上給出的是綜合語法,下麵將拆分進行講解
7.5.1 交叉連接
- 交叉連接(CROSS JOIN)作用於兩個關係上,並且第一個關係的每個元組與第二個關係的所有元組進行連接,這樣的操作形式與笛卡爾積是完全相同的,交叉連接的語法如下所示:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [CROSS JOIN 表2 表別名2]|
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
- 交叉連接的主要功能就是產生笛卡爾積
範例:使用交叉連接查詢信息
SELECT *
FROM emp CROSS JOIN dept;
- 一般而言,在進行多表連接的時候都一定會存在關聯欄位以消除笛卡爾積,而關聯欄位的名稱一般都會一樣,如果不一樣,也會有部分相同,現在討論的是一樣的情況,例如 deptno 欄位這就表示一樣,就可以利用自然連接來消除掉笛卡爾積
7.5.2 自然連接
- 自然連接(NATURAL JOIN)運算作用於兩個關係,最終會通過兩個關係產生出一個關係作為結果。與交叉連接(笛卡爾積)不同的是,自然連接只考慮那些在兩個關係模式中都出現的屬性上取值相同的元組對。自然連接的操作語法如下:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [NATURAL JOIN 表2 表別名2]|
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
範例:使用自然連接查詢信息
SELECT *
FROM emp NATURAL JOIN dept;
- 這個時候會把連接的欄位放在第一列上進行顯示,而且這種方式就是一種內連接的方式。
7.5.3 USING 子句
- 通過自然連接可以直接使用關聯欄位消除掉笛卡爾積,那麼如果現在的兩張表中沒有存在這種關聯欄位的話,就可以通過 USING 子句完成笛卡爾積的消除,USING 子句的語法如下:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [JOIN 表2 USING(關聯列名稱)]|
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
範例:使用 USING 子句查詢信息
SELECT *
FROM emp JOIN dept USING(deptno);
7.5.4 ON 子句
- 在之前編寫等值連接時,採用了關聯欄位進行笛卡爾積的消除,那麼用戶在 SQL:1999 語法之中通過 ON 子句就可以由用戶手工設置一個關聯條件,ON 子句語法如下:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [JOIN 表2 ON(關聯條件)]|
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
- USING 是設置連接欄位,而 ON 是設置連接條件
範例:使用 ON 子句查詢信息
SELECT *
FROM emp e JOIN salgrade s ON(e.sal BETWEEN s.losal AND s.hisal);
7.5.5 外連接
- 在數據的查詢操作中數據的外連接一共分為三種形式:左外連接、右外連接、全外連接,而此時連接的語法如下:
SELECT [DISTINCT] *|列名稱 [AS][列別名],列名稱 [AS][列別名],...
FROM 表1 表別名1 [LEFT|RIGHT|FULL OUTER JOIN 表2 ON(關聯條件)]
[WHERE 條件(s)]
[ORDER BY 排序的欄位1 ASC|DESC, 排序的欄位2 ASC|DESC,...];
- 對於外連接,在之前使用的是 “ (+) ”,這個標記只能夠實現左外或者右外連接,但是對於全外連接無法使用,而全外連接只能夠依靠 SQL:1999 語法之中規定的內容。
範例:實現右外連接
SELECT *
FROM emp e RIGHT OUTER JOIN dept d ON (e.deptno = d.deptno);
範例: 實現左外連接
SELECT *
FROM emp e LEFT OUTER JOIN dept d ON (e.deptno = d.deptno);
範例:實現全外連接
SELECT *
FROM emp e FULL OUTER JOIN dept d ON (e.deptno = d.deptno);
- 通過以上的分析可以發現,給出的所有語法裡面,只有全外連接只能夠通過 SQL:1999 語法實現,但是對於這種全外連接,使用的情況並不多。
- 而且個人建議,如果使用的是 Oracle 資料庫,就使用 “ (+) ” 標記控制左右連接,不使用它實現內連接。
7.6、數據的集合運算
- 數據的集合操作指的是查詢結果的操作
- 集合運算是一種二目運算符,一共包括四種運算符:並、差、交、笛卡爾積,其中對於笛卡爾積在之前已經演示過了,所以本次主要是看 並、交、差 三種操作。操作集合的語法如下
查詢語句
[UNION | UNION ALL | INTERSECT | MINUS]
查詢語句
...
- 要實現集合的運算,主要使用四種運算符:
- UNION (並集):返回若幹個查詢結果的全部內容,但是重覆元組不顯示;
- UNION ALL (並集):返回若幹個查詢結果的全部內容,重覆元組也會顯示;
- MINUS (差集):返回若幹個查詢結果中的不同部分
- INTERSECT (交集):返回若幹個查詢結果中的相同部分
7.6.1 並集操作
- 並集操作是將多個查詢的結果連接到一起,而對於並操作提供了兩種操作符:UNION(不顯示重覆),UNION ALL(顯示重覆)
範例:並集操作:UNION , UNION ALL
- 第一個查詢
SELECT * FROM dept;
- 第二個查詢
SELECT * FROM dept WHERE deptno = 10;
這個時候兩個查詢結果返回的列的結構相同。
- 使用 UNION
SELECT * FROM dept
UNION
SELECT * FROM dept WHERE deptno = 10;
第一個查詢已經包含了第二個查詢的內容,所以重覆數據不顯示了。
範例:使用 UNION ALL 顯示全部
SELECT * FROM dept
UNION ALL
SELECT * FROM dept WHERE deptno = 10;
- 提示:在以後進行查詢操作編寫過程中,建議儘量使用UNION 或 UNION ALL 來代替 OR
範例:查詢所有銷售人員和辦事人員的信息
- 實現一:
SELECT * FROM emp WHERE job = 'SALESMAN' OR job = 'CLERK';
- 實現二:
SELECT * FROM emp WHERE job IN ('SALESMAN', 'CLERK');
- 實現三:
SELECT * FROM emp WHERE job = 'SALESMAN'
UNION
SELECT * FROM emp WHERE job = 'CLERK'
7.6.2 差集操作
範例:使用 MINUS 執行差集操作
SELECT * FROM dept
MINUS
SELECT * FROM dept WHERE deptno = 10;
- 結果就顯示3行,deptno=10 的不顯示了
7.6.3 交集操作
範例:使用 INTERSECT 執行交集操作
SELECT * FROM dept
INTERSECT
SELECT * FROM dept WHERE deptno = 10;
- 結果只顯示 deptno=10 一行了
- 集合操作時,各個查詢語句返回的結構要求一致。
說明:本學習資料是根據李興華的Oracle開發實戰經典整理