Oracle筆記六:多表查詢

本次預計講解的知識點

1、 多表查詢的操作、限制、笛卡爾積的問題;

2、 統計函數及分組統計的操作;

3、 子查詢的操作,並且結合限定查詢、數據排序、多表查詢、統計查詢一起完成各個複雜查詢的操作;

一、多表查詢的基本概念

在之前所使用的查詢操作之中,都是從一張表之中查詢出所需要的內容,那麼如果現在一個查詢語句需要顯示多張表的數據,則就必須應用到多表查詢的操作,而多表查詢的語法如下:

複製代碼
SELECT [DISTINCT] * | 字段 [別名] [,字段 [別名] ,…]

FROM 表名稱 [別名], [表名稱 [別名] ,…]

[WHERE 條件(S)]

[ORDER BY 排序字段 [ASC|DESC] [,排序字段 [ASC|DESC] ,…]];
複製代碼

但是如果要進行多表查詢之前,首先必須先查詢出幾個數據 —— 僱員表和部門表中的數據量,這個操作可以通過COUNT()函數完成。

範例:查詢emp表中的數據量 ——返回了14條記錄

SELECT COUNT(*) FROM emp;

範例:查詢dept表中的數據量 ——4條記錄

SELECT COUNT(*) FROM dept;

額外補充一點:何爲經驗?

在日後的開發之中,很多人都肯定要接觸到許多新的數據庫和數據表,那麼在這種時候有兩種做法:

  • 做法一:新人做法,上來直接輸入以下的命令:
SELECT * FROM 表名稱;

如果此時數據量較大的話,一上無法瀏覽數據,二有可能造成系統的死機;

  • 做法二:老人做法,先看一下有多少條記錄:
SELECT COUNT(*) FROM 表名稱;

如果此時數據量較小,則可以查詢全部數據,如果數據量較大則不能直接使用SELECT查詢。

現在確定好了emp和dept表中的記錄之後,下面完成一個基本的多表查詢:

SELECT * FROM emp, dept;

但是現在查詢之後發現一共產生了56條記錄 = 僱員表的14條記錄 * 部門表的4條記錄,之所以會造成這樣的問題,主要都是由數據庫的查詢機制所決定的,例如,如下圖所示。

本問題在數據庫的操作之中被稱爲笛卡爾積,就表示多張表的數據乘積的意思,但是這種查詢結果肯定不是用戶所希望的,那麼該如何去掉笛卡爾積呢?

最簡單的方式是採用關聯字段的形式,emp表和dept表之間現在存在了deptno的關聯字段,所以現在可以從這個字段上的判斷開始。

當在查詢之中,不同的表中有了相同字段名稱的時候,訪問這些字段必須加上表名稱,即“表.字段”。

SELECT * FROM emp
WHERE emp.deptno=dept.deptno;

此時的查詢結果之中已經消除了笛卡爾積,但是現在只屬於顯示上的消除,而真正笛卡爾積現在依然存在,因爲數據庫的操作機制就屬於逐行的進行數據的判斷,那麼如果按照這個思路理解的話,現在假設兩張表的數據量都很大的話,那麼使用這種多表查詢的性能。

範例:以sh用戶的大數據表爲例

SELECT COUNT(*) FROM sales, costs
WHERE sales.prod_id=costs.prod_id;

這兩張表即便消除了笛卡爾積的顯示,但是本身也會有笛卡爾積的問題,所以最終的查詢結果會很慢顯示,甚至是不顯示,所以通過這道程序一定要記住,多表查詢的性能是很差的,當然,性能差是有一個前提的:數據量大。

但是以上的程序也存在一個問題,在之前訪問表中字段的時候使用的是“表.字段”名稱,那麼如果說現在假設表名稱很長,例如“yinhexi_diqiu_yazhou_zhongguo_beijing_xicheng_ren”,所以一般在進行多表查詢的時候往往都會爲表起一個別名,通過別名.字段的方式進行查詢。

SELECT * FROM emp e, dept d
WHERE e.deptno=d.deptno;

範例:查詢出每一位僱員的編號、姓名、職位、部門名稱、位置

1、確定所需要的數據表:

  • emp表:可以查詢出僱員的編號、姓名、職位;
  • dept表:可以查詢出部門名稱和位置;

2、確定表的關聯字段:emp.deptno=dept.deptno;

第一步:查詢出每一位僱員的編號、姓名、職位

SELECT e.empno, e.ename, e.job
FROM emp e;

第二步:爲查詢中引入部門表,同時需要增加一個消除笛卡爾積的條件

SELECT e.empno, e.ename, e.job, d.dname, d.loc
FROM emp e, dept, d
WHERE e.deptno=d.deptno;

以後遇到問題,發現沒有解決問題的思路,就按照上面的步驟進行,慢慢的分析解決,因爲多表查詢不可能一次性全部寫出,需要逐步分析的。

範例:要求查詢出每一位僱員的姓名、職位、領導的姓名。

現在肯定要準備出兩個emp表,所以這個時候可以稱爲emp表的自身關聯,按照之前的分析如下:

1、確定所需要的數據表:

  • emp表(僱員):取得僱員的姓名、職位、領導編號;
  • emp表(領導):取得僱員的姓名(領導的姓名);

2、確定關聯字段:emp.mgr=memp.empno(僱員的領導編號 = 領導(僱員)的僱員編號)

第一步:查詢每一位僱員的姓名、職位

SELECT e.ename, e.job
FROM emp e;

第二步:查詢領導信息,加入自身關聯

SELECT e.ename, e.job, m.ename
FROM emp e, emp m
WHERE e.mgr=m.empno;

此時的查詢結果之中缺少了“KING”的記錄,因爲KING沒有領導,而要想解決這個問題,就需要等待之後講解的左、右連接的問題了。

範例:查詢出每個僱員的編號、姓名、基本工資、職位、領導的姓名、部門名稱及位置。

1、確定所需要的數據表:

  • emp表:每個僱員的編號、姓名、基本工資、職位;
  • emp表(領導):領導的姓名;
  • dept表:部門的名稱及位置。

2、確定已知的關聯字段:

  • 僱員和部門:emp.deptno=dept.deptno;
  • 僱員和領導:emp.mgr=memp.empno;

第一步:查詢出每個僱員的編號、姓名、基本工資、職位

SELECT empno, ename, sal, job
FROM emp;

第二步:加入領導的信息,引入自身關聯,同時增加消除笛卡爾積的條件

SELECT e.empno, e.ename, e.sal, e.job, m.ename
FROM emp e, emp m
WHERE e.mgr=m.empno;

第三步:加入部門的信息,引入dept表,既然有新的表進來,則需要繼續增加消除笛卡爾積的條件

SELECT e.empno, e.ename, e.sal, e.job, m.ename, d.dname, d.loc
FROM emp e, emp m, dept d
WHERE e.mgr=m.empno AND e.deptno=d.deptno;

所以以後的所有類似的問題最好都能夠按照如上的方式編寫,形成自己的思路。

思考題:現在要求查詢出每一個僱員的編號、姓名、工資、部門名稱、工資所在公司的工資等級。

1、確定所需要的數據表:

  • emp表:僱員的編號、姓名、工資;
  • dept表:部門名稱;
  • salgrade表:工資等級;

2、確定已知的關聯字段:

  • 僱員和部門:emp.deptno=dept.deptno;
  • 僱員和工資等級:emp.sal BETWEEN salgrade.losal AND salgrade.hisal;

第一步:查詢出每一個僱員的編號、姓名、工資

SELECT e.empno, e.ename, e.sal
FROM emp e;

第二步:引入部門表,同時增加一個消除笛卡爾積的條件

SELECT e.empno, e.ename, e.sal, d.dname
FROM emp e, dept d
WHERE e.deptno=d.deptno;

第三步:引入工資等級表,繼續增加消除笛卡爾積的條件

SELECT e.empno, e.ename, e.sal, d.dname, s.grade
FROM emp e, dept d, salgrade s
WHERE e.deptno=d.deptno AND e.sal BETWEEN s.losal AND s.hisal;

如果現在有如下的進一步要求:將每一個工資等級替換成具體的文字信息,例如:

1 替換成 第五等工資、2 替換成 第四等工資、3 替換成 第三等工資,依次類推 –> 依靠DECODE()實現

SELECT e.empno, e.ename, e.sal, d.dname
DECODE(s.grade,1,’第五等工資’,2,’第四等工資’,3,’第三等工資’,4,’第二等工資’,5,’第一等工資’) gradeinfo
FROM emp e, dept d, salgrade s
WHERE e.deptno=d.deptno AND e.sal BETWEEN s.losal AND s.hisal;

以後的所有的題目都按照類似的方式分析,只要是表關聯,肯定有關聯字段,用於消除笛卡爾積,只是這種關聯字段需要根據情況使用不同的限定符號。

二、左、右連接

關於左、右連接指的是查詢判斷條件的參考方向,例如,下面有如下查詢:

SELECT * FROM emp e, dept d WHERE e.deptno=d.deptno;
複製代碼
     EMPNO ENAME      JOB              MGR HIREDATE              SAL       COMM     DEPTNO     DEPTNO DNAME          LOC
---------- ---------- --------- ---------- -------------- ---------- ---------- ---------- ---------
      7782 CLARK      MANAGER         7839 09-6月 -81           2450                    10         10 ACCOUNTING     NEW YORK
      7839 KING       PRESIDENT            17-11月-81           5000                    10         10 ACCOUNTING     NEW YORK
      7934 MILLER     CLERK           7782 23-1月 -82           1300                    10         10 ACCOUNTING     NEW YORK
      7369 SMITH      CLERK           7902 17-12月-80            800                    20         20 RESEARCH       DALLAS
      7876 ADAMS      CLERK           7788 23-5月 -87           1100                    20         20 RESEARCH       DALLAS
      7902 FORD       ANALYST         7566 03-12月-81           3000                    20         20 RESEARCH       DALLAS
      7788 SCOTT      ANALYST         7566 19-4月 -87           3000                    20         20 RESEARCH       DALLAS
      7566 JONES      MANAGER         7839 02-4月 -81           2975                    20         20 RESEARCH       DALLAS
      7499 ALLEN      SALESMAN        7698 20-2月 -81           1600        300         30         30 SALES          CHICAGO
      7698 BLAKE      MANAGER         7839 01-5月 -81           2850                    30         30 SALES          CHICAGO
      7654 MARTIN     SALESMAN        7698 28-9月 -81           1250       1400         30         30 SALES          CHICAGO
      7900 JAMES      CLERK           7698 03-12月-81            950                    30         30 SALES          CHICAGO
      7844 TURNER     SALESMAN        7698 08-9月 -81           1500          0         30         30 SALES          CHICAGO
      7521 WARD       SALESMAN        7698 22-2月 -81           1250        500         30         30 SALES          CHICAGO

已選擇14行。
複製代碼

部門一共有四個,但是現在只返回了三個部門的信息,缺少40部門,因爲在僱員表之中沒有一條記錄是屬於40部門的,所以現在不會顯示40部門的信息,即:現在的查詢以emp表爲參考,那麼如果說現在非要顯示40部門呢?就必須改變這種參考的方向,就需要用使用左、右連接。

SELECT * FROM emp e, dept d WHERE e.deptno(+)=d.deptno;
複製代碼
     EMPNO ENAME      JOB              MGR HIREDATE              SAL       COMM     DEPTNO     DEPTNO DNAME          LOC
---------- ---------- --------- ---------- -------------- ---------- ---------- ---------- ---------
      7782 CLARK      MANAGER         7839 09-6月 -81           2450                    10         10 ACCOUNTING     NEW YORK
      7839 KING       PRESIDENT            17-11月-81           5000                    10         10 ACCOUNTING     NEW YORK
      7934 MILLER     CLERK           7782 23-1月 -82           1300                    10         10 ACCOUNTING     NEW YORK
      7369 SMITH      CLERK           7902 17-12月-80            800                    20         20 RESEARCH       DALLAS
      7876 ADAMS      CLERK           7788 23-5月 -87           1100                    20         20 RESEARCH       DALLAS
      7902 FORD       ANALYST         7566 03-12月-81           3000                    20         20 RESEARCH       DALLAS
      7788 SCOTT      ANALYST         7566 19-4月 -87           3000                    20         20 RESEARCH       DALLAS
      7566 JONES      MANAGER         7839 02-4月 -81           2975                    20         20 RESEARCH       DALLAS
      7499 ALLEN      SALESMAN        7698 20-2月 -81           1600        300         30         30 SALES          CHICAGO
      7698 BLAKE      MANAGER         7839 01-5月 -81           2850                    30         30 SALES          CHICAGO
      7654 MARTIN     SALESMAN        7698 28-9月 -81           1250       1400         30         30 SALES          CHICAGO
      7900 JAMES      CLERK           7698 03-12月-81            950                    30         30 SALES          CHICAGO
      7844 TURNER     SALESMAN        7698 08-9月 -81           1500          0         30         30 SALES          CHICAGO
      7521 WARD       SALESMAN        7698 22-2月 -81           1250        500         30         30 SALES          CHICAGO
                                                                                                   40 OPERATIONS     BOSTON

已選擇15行。
複製代碼

現在發現40部門出現了,所以發現參考的方向已經改變了,而“(+)”就用於左、右連接的更改,這種符號有以下兩種使用情況:

  • (+)=:放在了等號的左邊,表示的是右連接;
  • =(+):放在了等號的右邊,表示的是左連接;

但是不用去刻意的區分是左還是右,只是根據查詢結果而定,如果發現有些需要的數據沒有顯示出來,就使用此符號更改連接方向。

範例:查詢每個僱員的姓名和領導的姓名

SELECT e.ename, e.job, m.ename
FROM emp e, emp m
WHERE e.mgr=m.empno(+);

可是這種符號是Oracle數據庫自己所獨有的,其他數據庫不能使用。

三、SQL:1999語法

除了以上的表連接操作之外,在SQL語法之中,也提供了另外一套用於表連接的操作SQL,格式如下:

複製代碼
SELECT table1.column,table2.column

FROM table1 [CROSS JOIN table2]|

[NATURAL JOIN table2]|

[JOIN table2 USING(column_name)]|

[JOIN table2 ON(table1.column_name=table2.column_name)]|

[LEFT|RIGHT|FULL OUTER JOIN table2 ON(table1.column_name=table2.column_name)];
複製代碼

以上實際上是屬於多個語法的聯合,下面分塊說明語法的使用。

1、交叉連接(CROSS JOIN):用於產生笛卡爾積

SELECT * FROM emp CROSS JOIN dept;

笛卡爾積本身並不是屬於無用的內容,在某些情況下還是需要使用的。

2、自然連接(NATURAL JOIN):自動找到匹配的關聯字段,消除掉笛卡爾積

SELECT * FROM emp NATURAL JOIN dept;

但是並不是所有的字段都是關聯字段,設置關聯字段需要通過約束指定;

3、JOIN…USING子句:用戶自己指定一個消除笛卡爾積的關聯字段

SELECT * FROM emp JOIN dept USING(deptno);

4、JOIN…ON子句:用戶自己指定一個可以消除笛卡爾積的關聯條件

SELECT * FROM emp JOIN dept ON(emp.deptno=dept.deptno);

5、連接方向的改變:

  • 左(外)連接:LEFT OUTER JOIN…ON;
  • 右(外)連接:RIGHT OUTER JOIN…ON;
  • 全(外)連接:FULL OUTER JOIN…ON; –> 把兩張表中沒有的數據都顯示
SELECT * FROM emp RIGHT OUTER JOIN dept ON(emp.deptno=dept.deptno);

在Oracle之外的數據庫都使用以上的SQL:1999語法操作,所以這個語法還必須會一些(如果你一直使用的都是Oracle就可以不會了)。

再次強調:多表查詢的性能肯定不高,而且性能一定要在大數據量的情況下才能夠發現。

四、統計函數及分組查詢

1、統計函數

在之前學習過一個COUNT()函數,此函數的功能可以統計出表中的數據量,實際上這個就是一個統計函數,而常用的統計函數有如下幾個:

  • COUNT():查詢表中的數據記錄;
  • AVG():求出平均值;
  • SUM():求和;
  • MAX():求出最大值;
  • MIN():求出最小值;

範例:測試COUNT()、AVG()、SUM()

統計出公司的所有僱員,每個月支付的平均工資及總工資。

SELECT MAX(sal),MIN(sal) FROM emp;

注意點:關於COUNT()函數

COUNT()函數的主要功能是進行數據的統計,但是在進行數據統計的時候,如果一張表中沒有統計記錄,COUNT()也會返回數據,只是這個數據是“0”。

SELECT COUNT(ename) FROM BONUS;

如果使用的是其他函數,則有可能返回null,但是COUNT()永遠都會返回一個具體的數字,這一點以後在開發之中都會使用到。

2、分組查詢

在講解分組操作之前首先必須先明確一點,什麼情況下可能分組,例如:

  • 公司的所有僱員,要求男性一組,女性一組,之後可以統計男性和女性的數量;
  • 按照年齡分組,18歲以上的分一組,18歲以下的分一組;
  • 按照地區分組:北京人一組,上海人一組,四川一組;

這些信息如果都保存了數據庫之中,肯定在數據的某一列上會存在重複的內容,例如:按照性別分組的時候,性別肯定有重複(男和女),按照年齡分組(有一個範圍的重複),按照地區分組有一個地區的信息重複。

所以分組之中有一個不成文的規定:當數據重複的時候分組纔有意義,因爲一個人也可以一組(沒什麼意義)。

複製代碼
SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數

FROM 表名稱 [別名], [表名稱 [別名] ,…]

[WHERE 條件(s)]

[GROUP BY 分組字段1 [,分組字段2 ,…]]

[ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]];
複製代碼

範例:按照部門編號分組,求出每個部門的人數,平均工資

SELECT deptno, COUNT(empno), AVG(sal)
FROM emp
GROUP BY deptno;

範例:按照職位分組,求出每個職位的最高和最低工資

SELECT job, MAX(sal), MIN(sal)
FROM emp
GROUP BY job;

但是現在一旦分組之後,實際上對於語法上就會出現了新的限制,對於分組有以下要求:

  • 分組函數可以在沒有分組的時候單獨用使用,可是卻不能出現其他的查詢字段;

分組函數單獨使用:

SELECT COUNT(empno) FROM emp;

錯誤的使用,出現了其他字段:

SELECT empno,COUNT(empno) FROM emp;

  • 如果現在要進行分組的話,則SELECT子句之後,只能出現分組的字段和統計函數,其他的字段不能出現:

正確做法:

SELECT job,COUNT(empno),AVG(sal)

FROM emp

GROUP BY job;

錯誤的做法:

SELECT deptno,job,COUNT(empno),AVG(sal)

FROM emp

GROUP BY job;

  • 分組函數允許嵌套,但是嵌套之後的分組函數的查詢之中不能再出現任何的其他字段。

範例:按照職位分組,統計平均工資最高的工資

1、先統計出各個職位的平均工資

SELECT job,AVG(sal)
FROM emp
GROUP BY job;

2、平均工資最高的工資

SELECT MAX(AVG(sal))
FROM emp
GROUP BY job;

範例:查詢出每個部門的名稱、部門的人數、平均工資

1、確定所需要的數據表:

  • dept表:每個部門的名稱;
  • emp表:統計出部門的人數、平均工資;

2、確定已知的關聯字段:emp.deptno=dept.deptno;

範例:將dept表和emp表的數據關聯

SELECT d.dname,e.empno,e.sal
FROM dept d, emp e
WHERE d.deptno=e.deptno;
複製代碼
DNAME               EMPNO        SAL
-------------- ---------- ----------
ACCOUNTING           7782       2450
ACCOUNTING           7839       5000
ACCOUNTING           7934       1300
RESEARCH             7369        800
RESEARCH             7876       1100
RESEARCH             7902       3000
RESEARCH             7788       3000
RESEARCH             7566       2975
SALES                7499       1600
SALES                7698       2850
SALES                7654       1250
SALES                7900        950
SALES                7844       1500
SALES                7521       1250

已選擇14行。
複製代碼

此時的查詢結果中,可以發現在dname字段上顯示出了重複的數據,按照之前對分組的理解,只要數據重複了,那麼就有可能進行分組的查詢操作,但是此時與之前的分組不太一樣,之前的分組是針對於一張實體表進行的分組(emp、dept都屬於實體表),但是對於以上的數據是通過查詢結果顯示的,所以是一張臨時的虛擬表,但是不管是否是實體表還是虛擬表,只要是有重複,那麼就直接進行分組。

SELECT d.dname,COUNT(e.empno),AVG(e.sal)
FROM dept d, emp e
WHERE d.deptno=e.deptno
GROUP BY d.dname;

但是這個分組並不合適,因爲部門一共有四個部門(因爲現在已經引入了dept表,dept表存在了四個部門的信息),所以應該通過左右連接改變查詢的結果。

SELECT d.dname,COUNT(e.empno),NVL(AVG(e.sal),0)
FROM dept d, emp e
WHERE d.deptno=e.deptno(+)
GROUP BY d.dname;

之前的所有操作都是針對於單個字段分組的,而實際上分組操作之中也可以實現多字段分組。

範例:要求顯示每個部門的編號、名稱、位置、部門的人數、平均工資

1、確定所需要的數據表:

  • dept表:每個部門的名稱;
  • emp表:統計出部門的人數、平均工資;

2、確定已知的關聯字段:emp.deptno=dept.deptno;

範例:將emp表和dept表關聯查詢

SELECT d.deptno,d.dname,d.loc,e.empno,e.sal
FROM dept d,emp e
WHERE d.deptno=e.deptno(+);
複製代碼
    DEPTNO DNAME          LOC                EMPNO        SAL
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK            7782       2450
        10 ACCOUNTING     NEW YORK            7839       5000
        10 ACCOUNTING     NEW YORK            7934       1300
        20 RESEARCH       DALLAS              7369        800
        20 RESEARCH       DALLAS              7876       1100
        20 RESEARCH       DALLAS              7902       3000
        20 RESEARCH       DALLAS              7788       3000
        20 RESEARCH       DALLAS              7566       2975
        30 SALES          CHICAGO             7499       1600
        30 SALES          CHICAGO             7698       2850
        30 SALES          CHICAGO             7654       1250
        30 SALES          CHICAGO             7900        950
        30 SALES          CHICAGO             7844       1500
        30 SALES          CHICAGO             7521       1250
        40 OPERATIONS     BOSTON

已選擇15行。
複製代碼

此時存在了重複數據,而且這個重複的數據平均在了三列上(deptno,dname,loc),所以在分組上的GROUP BY子句中就可以寫上三個字段:

SELECT d.deptno,d.dname,d.loc,COUNT(e.empno),NVL(AVG(e.sal),0)
FROM dept d,emp e
WHERE d.deptno=e.deptno(+)
GROUP BY d.deptno,d.dname,d.loc;

以上就是多字段分組,但是不管是單字段還是多字段,一定要有一個前提,存在了重複數據。

範例:要求統計出每個部門的詳細信息,並且要求這些部門的平均工資高於2000;

在以上程序的基礎上完成開發,在之前唯一所學習的限定查詢的語法只有WHERE子句,所以下面先使用WHERE完成要求。

SELECT d.deptno,d.dname,d.loc,COUNT(e.empno) mycount,NVL(AVG(e.sal),0) myavg
FROM dept d,emp e
WHERE d.deptno=e.deptno(+) AND AVG(e.sal)>2000
GROUP BY d.deptno,d.dname,d.loc;

現在出現瞭如下的錯誤提示:

WHERE d.deptno=e.deptno(+) AND AVG(e.sal)>2000
                               *
第 3 行出現錯誤:
ORA-00934: 此處不允許使用分組函數

本錯誤提示的核心意思就是在WHERE子句之中不能使用統計函數,之所以在WHERE子句之中不能使用,實際上跟WHERE子句的主要功能有關,WHERE的主要功能是從全部的數據之中取出部分數據。

此時如果要對分組後的數據再次進行過濾,則使用HAVING子句完成,那麼此時的SQL語法格式如下:

複製代碼
SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數

FROM 表名稱 [別名], [表名稱 [別名] ,…]

[WHERE 條件(s)]

[GROUP BY 分組字段1 [,分組字段2 ,…]]

[HAVING 分組後的過濾條件(可以使用統計函數)]

[ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]];
複製代碼

下面使用HAVING進行過濾。

SELECT d.deptno,d.dname,d.loc,COUNT(e.empno) mycount,NVL(AVG(e.sal),0) myavg
FROM dept d,emp e
WHERE d.deptno=e.deptno(+)
GROUP BY d.deptno,d.dname,d.loc
HAVING AVG(sal)>2000;

注意點:WHERE和HAVING的區別

  • WHERE:是在執行GROUP BY操作之前進行的過濾,表示從全部數據之中篩選出部分的數據,在WHERE之中不能使用統計函數;
  • HAVING:是在GROUP BY分組之後的再次過濾,可以在HAVING子句中使用統計函數;

思考題:顯示非銷售人員工作名稱以及從事同一工作僱員的月工資的總和,並且要滿足從事同一工作的僱員的月工資合計大於$5000,輸出結果按月工資的合計升序排列:

第一步:查詢出所有的非銷售人員的信息

SELECT * FROM emp WHERE job<>'SALESMAN';

第二步:按照職位進行分組,並且使用SUM函數統計

SELECT job,SUM(sal)
FROM emp
WHERE job<>'SALESMAN'
GROUP BY job;

第三步:月工資的合計是通過統計函數查詢的,所以現在這個對分組後的過濾要使用HAVING子句完成

SELECT job,SUM(sal)
FROM emp
WHERE job<>'SALESMAN'
GROUP BY job
HAVING SUM(sal)>5000;

第四步:按照升序排列

複製代碼
SELECT job,SUM(sal) sum
FROM emp
WHERE job<>'SALESMAN'
GROUP BY job
HAVING SUM(sal)>5000
ORDER BY sum ASC;
複製代碼

以上的題目就融合分組操作的大部分語法的使用,而且以後遇到問題,要慢慢分析。

五、子查詢

子查詢 = 簡單查詢 + 限定查詢 + 多表查詢 + 統計查詢的綜合體;

在之前強調過多表查詢不建議大家使用,因爲性能很差,但是多表查詢最有利的替代者就是子查詢,所以子查詢在實際的開發之中使用的相當的多;

所謂的子查詢指的就是在一個查詢之中嵌套了其他的若干查詢,嵌套子查詢之後的查詢SQL語句如下:

複製代碼
SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數 ,(

      SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數

      FROM 表名稱 [別名], [表名稱 [別名] ,…]

      [WHERE 條件(s)]

      [GROUP BY 分組字段1 [,分組字段2 ,…]]

      [HAVING 分組後的過濾條件(可以使用統計函數)]

      [ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]])

FROM 表名稱 [別名], [表名稱 [別名] ,…] ,(

      SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數

      FROM 表名稱 [別名], [表名稱 [別名] ,…]

      [WHERE 條件(s)]

      [GROUP BY 分組字段1 [,分組字段2 ,…]]

      [HAVING 分組後的過濾條件(可以使用統計函數)]

      [ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]])

[WHERE 條件(s) (

      SELECT [DISTINCT] *|分組字段1 [別名] [,分組字段2 [別名] ,…] | 統計函數

      FROM 表名稱 [別名], [表名稱 [別名] ,…]

      [WHERE 條件(s)]

      [GROUP BY 分組字段1 [,分組字段2 ,…]]

      [HAVING 分組後的過濾條件(可以使用統計函數)]

      [ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]])]

[GROUP BY 分組字段1 [,分組字段2 ,…]]

[HAVING 分組後的過濾條件(可以使用統計函數)]

[ORDER BY 排序字段 ASC | DESC [,排序字段 ASC | DESC]];
複製代碼

理論上子查詢可以出現在查詢語句的任意位置上,但是從個人而言,子查詢出現在WHERE和FROM子句之中較多;

以下的使用特點爲個人總結,不是官方聲明的:

  • WHERE:子查詢一般只返回單行列、多行單列、單行多列的數據;
  • FROM:子查詢返回的一般是多行的數據,當作一張臨時表出現。

範例:要求查詢出工資比SMITH還要高的全部僱員信息

要想完成本程序,首先必須要知道SMITH的工資是多少:

SELECT sal FROM emp WHERE ename='SMITH';

由於此時返回的是單列的數據,所以這個子句查詢可以在WHERE中出現。

SELECT * FROM emp
WHERE sal>(
SELECT sal
FROM emp
WHERE ename='SMITH');

範例:要求查詢出高於公司平均工資的全部僱員信息

公司的平均工資應該使用AVG()函數求出。

SELECT AVG(sal) FROM emp;

此時數據的返回結果是單行單列的數據,在WHERE之中出現。

SELECT * FROM emp
WHERE sal>(
SELECT AVG(sal)
FROM emp);

以上所返回的是單行單列,但是在子查詢之中,也可以返回單行多列的數據,只是這種子查詢很少出現。

範例:子查詢返回單行多列數據

SELECT * FROM emp
WHERE (job,sal)=(
SELECT job,sal
FROM emp
WHERE ename='ALLEN');

如果現在的子查詢返回的是多行單列數據的話,這個時候就需要使用三種判斷符判斷了:IN、ANY、ALL;

1、 IN操作符:用於指定一個子查詢的判斷範圍

這個操作符的使用實際上與之前講解的IN是一樣的,唯一不同的是,裏面的範圍由子查詢指定了。

SELECT * FROM emp
WHERE sal in (
SELECT sal
FROM emp
WHERE job='MANAGER');

但是在使用IN的時候還要注意NOT IN的問題,如果使用NOT IN操作,在子查詢之中,如果有一個內容是null,則不會查詢出任何的結果。

2、 ANY操作符:與每一個內容想匹配,有三種匹配形式

  • =ANY:功能與IN操作符是完全一樣的;
SELECT * FROM emp
WHERE sal=ANY (
SELECT sal
FROM emp
WHERE job='MANAGER');
  • >ANY:比子查詢中返回記錄最小的還要大的數據;
SELECT * FROM emp
WHERE sal>ANY (
SELECT sal
FROM emp
WHERE job='MANAGER');
  • <ANY:比子查詢中返回記錄的最大的還要小;
SELECT * FROM emp
WHERE sal<ANY (
SELECT sal
FROM emp
WHERE job='MANAGER');

3、 ALL操作符:與每一個內容相匹配,有兩種匹配形式:

  • >ALL:比子查詢中返回的最大的記錄還要大
SELECT * FROM emp
WHERE sal>ALL (
SELECT sal
FROM emp
WHERE job='MANAGER');
  • <ALL:比子查詢中返回的最小的記錄還要小
SELECT * FROM emp
WHERE sal<ALL (
SELECT sal
FROM emp
WHERE job='MANAGER');

以上的所有子查詢都是在WHERE子句中出現的,那麼下面再來觀察在FROM子句中出現的查詢,這個子查詢一般返回的是多行多列的數據,當作一張臨時表的方式來處理。

範例:查詢出每個部門的編號、名稱、位置、部門人數、平均工資

  • 回顧:最早的時候使用的是多字段分組統計完成的:
SELECT d.deptno,d.dname,d.loc,COUNT(e.empno),AVG(e.sal)
FROM emp e,dept d
WHERE e.deptno(+)=d.deptno
GROUP BY d.deptno,d.dname,d.loc;

這個時候實際上是產生了笛卡爾積,一共產生了56條記錄;

  • 新的解決方案:通過子查詢完成,所有的統計查詢只能在GROUP BY中出現,所以在子查詢之中負責統計數據,而在外部的查詢之中,負責將統計數據和dept表數據相統一。
複製代碼
SELECT d.deptno,d.dname,d.loc,temp.count,temp.avg
FROM dept d,(
SELECT deptno dno,COUNT(empno) count,AVG(sal) avg
FROM emp
GROUP BY deptno) temp
WHERE d.deptno=temp.dno(+);
複製代碼

現在的程序中所操作的數據量:

  • 子查詢中統計的記錄是14條記錄,最終統計的顯示結果是3條記錄;
  • dept表之中一共有4條記錄;
  • 如果現在產生笛卡爾積的話只有12條記錄,再加上僱員的14條記錄,一共才26條記錄;

通過如上的分析,可以發現,使用子查詢的確要比使用多表查詢更加節省性能,所以在開發之中子查詢出現是最多的,而且在給出一個不成文的規定:大部分情況下,如果最終的查詢結果之中需要出現SELECT子句,但是又不能直接使用統計函數的時候,就在子查詢中統計信息,即:有複雜統計的地方大部分都需要子查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章