1. 程式人生 > >資料庫知識整理 - 關係資料庫標準語言SQL(二)- 超良心的資料查詢整理!

資料庫知識整理 - 關係資料庫標準語言SQL(二)- 超良心的資料查詢整理!

主要內容

1. 選擇表中的若干列

2. 選擇表中的若干元組

3. ORDER BY子句

4. 聚集函式

5. GROUP BY子句

1. 等值與非等值連線查詢

2. 自身連線

3. 外連線

4. 多表連線

1.帶有IN謂詞的子查詢

2. 帶有比較運算子的子查詢

3. 帶有ANY(SOME)或ALL謂詞的子查詢

4. 帶有EXISTS謂詞的子查詢


資料查詢

資料查詢是資料庫的核心操作

,該篇將著重介紹資料查詢的五種方式:單表查詢、連線查詢、巢狀查詢、集合查詢和基於派生表的查詢。


 

基本語法

一般格式(尖括號“<>”表示必填,“[]”表示可選,“|”用於區別不同選擇):

 

SELECTALL DISTINCT ] <目標列表達式> [,<其餘目標列表達式>]

FROM <表名或檢視名> [,<其餘表名或檢視名>]

[ WHERE <條件表示式> ]

[ GROUP BY <列名> [ HAVING

<條件表示式> ] ]

[ ORDER BY <列名> [ ASC | DESC ] ] ;

 

(1)整個SELECT語句的含義是,根據WHERE子句條件表示式,從FROM子句指定的範圍(基本表、檢視或派生表)中找出滿足條件的元組,再按SELECT子句中的目標列表達式選出元組中的屬性值形成結果表。

(2)形成的結果表可能會含有重複行,若SELECT子句中指定ALL關鍵詞,則保留結果表中的所有重複行。反之,若指定DISTINCT關鍵詞,則去掉重複行。預設為ALL

(3)GROUP BY子句用於對查詢結果(執行SELECT-FROM語句後的結果)屬性值

(同一列或幾列相應的值)相等的規則進行分組,隨後可用HAVING子句對劃分好的組依組處理。

(4)ORDER BY子句用於對查詢結果根據某一列或幾列的屬性值,按升序(ASC)降序(DESC)排列。

 

可能看完上面這一坨拗口(雖然我已經很努力在概括了)的句子後,大家還是雲裡霧裡的。不用擔心,後面會繼續作介紹。


 

單表查詢

單表查詢是指僅涉及一個表的查詢。

 

1. 選擇表中的若干列

SELECT-FROM語句對應關係代數的“投影(我是連結)

(1)指定查詢

如查詢全體學生的學號與名字:

SELECT Sno,Sname

FROM Student;

 

(2)全部查詢

如查詢全體學生的詳細資訊(學號,姓名,性別,年齡,院系):

SELECT Sno,Sname,Ssex,Sage,Sdept

FROM Student;

SQL提供了用於全選語法糖,如果你允許結果表中屬性列的排列順序與原表相同,則上面的語句等價於

SELECT *

FROM Student;

 

(3)表示式查詢

如查詢學生姓名、出生年份(假設今年是2018年)和所在院系,並且所在院系用小寫字母表示:

SELECT Sname, 'Year of Birth:', 2018-Sage BIRTHDAY, LOWER(Sdept) DEPARTMENT

FROM Student;

分析:

'Year of Birth:' —— 字串常量直接輸出

2018-Sage BIRTHDAY —— 算術表示式求出生年份,並且該列屬性名取別名為“BIRTHDAY

LOWER(Sdept) DEPARTMENT —— 使用SQL函式LOWER()實現字母轉換成小寫的功能,並取別名為“DEPARTMENT

 

2. 選擇表中的若干元組

(1)對重複行的處理判斷

SQL語句先處理列,再處理行。兩個本來不完全相同的元組,在投影到某些列後,可能會出現重複行,若指定DISTINCT關鍵詞,重複行會被消除,若指定ALL關鍵詞,則重複行被保留,系統預設為ALL

 

(2)查詢滿足條件的元組

SELECT-FROM-WHERE語句對應關係代數的“選擇”。

常用查詢條件
查詢條件 謂詞
比較 [NOT]比較運算子
確定範圍(包括端點) [NOT] BETWEEN AND
確定集合 [NOT] IN
字元匹配 [NOT] LIKE
空值 IS NULL,IS NOT NULL
多重條件 AND,OR,NOT

//查詢年齡在20~23歲之間(包括20和23歲)的學生的姓名:

SELECT Sname

FROM Student

WHERE Sage BETWEEN 20 AND 23;

//查詢電腦科學(CS)、數學系(MA)的學生姓名:

SELECT Sname

FROM Student

WHERE Sdept IN ('CS','MA');

//查詢無成績的學生的姓名和學號:

SELECT Sname,Sno

FROM Student

WHERE Grade IS NULL;

 

(3)字元匹配

在字元匹配中,“LIKE”等價於“=”。

此外,在字元匹配中涉及到兩個萬用字元%_)和換碼字元短語ESCAPE '\'

%代表任意長度的字串,例如a%b,表示以a為開頭,b為結尾的字串;

_代表任意單個字元;

ESCAPE '\'表示“\”後緊跟的字元“%”或“_”不作為萬用字元使用。

 

//查詢所有姓劉的學生的姓名和學號:

SELECT Sname,Sno

FROM Student

WHERE Sname LIKE '劉%';

//查詢姓“歐陽”且名字只有三個字的學生的姓名和學號:

SELECT Sname,Sno

FROM Student

WHERE Sname LIKE '歐陽_'

//查詢DB_Design課程的課程號和學分:

SELECT Cno,Ccredit

FROM Student

WHERE Cname LIKE 'DB\_Design' ESCAPE '\';

 

3. ORDER BY子句

//查詢選修了3號課程的學生的學號及其成績,查詢結果按分數的降序排序:

SELECT Sno,Grade

FROM SC

WHERE Cno = '3'

ORDER BY Grade DESC;

需要留意的是,對於空值,排序時顯示的順序由具體系統來決定。

 

4. 聚集函式

常用聚集函式
COUNT(*) 統計元組個數
COUNT( [ DISTINCT | ALL ] ) 統計一列中值的個數
SUM( [ DISTINCT | ALL ] ) 計算一列值的總和
AVG( [ DISTINCT | ALL ] ) 計算一列值的平均值
MAX( [ DISTINCT | ALL ] ) 求一列值中的最大值
MIN( [ DISTINCT | ALL ] ) 求一列值中的最小值

注意點:

(1)指定DISTINCT關鍵詞,表示計算時要忽略重複值。如果不指定DISTINCT,則預設為ALL關鍵詞,將重複值計算在內。

(2)WHERE子句不能用聚集函式作為條件表示式,聚集函式只能在SELECT子句HAVING子句中使用。WHERE子句HAVING子句的區別在於作用物件不同。WHERE子句作用於基本表或檢視,從中選擇滿足條件的元組,而HAVING子句作用於分組,從中選擇滿足條件的組。

(3)除COUNT(*)外,其他聚集函式遇到空值後都會自動跳過。

//查詢學生總人數:

SELECT COUNT(*)

FROM Student;

//查詢選修了課程的學生人數:

SELECT COUNT(DISTINCT Sno)

FROM SC;

//計算選修1號課程的學生平均分:

SELECT AVG(Grade)

FROM SC

WHERE Cno = '1';

 

5. GROUP BY子句

(1)GROUP BY子句將查詢結果按一列或多列的屬性值分組,組相等的行歸為一組。若不對查詢結果進行分組,聚集函式將作用於整個查詢結果。分組後,聚集函式會作用於每個小組並返回一個函式值。

//求各課程號及相應的選課人數:

SELECT Cno,COUNT(Sno)            /*分組後,COUNT函式會對每個組都進行計算*/

FROM SC

GROUP BY Cno;                             /*對查詢結果按Cno的值分組*/

 

(2)GROUP BY子句還可以配合上HAVING短語(回顧上面的注意點2)指定篩選條件。

//查詢選修了三門以上課程的學生學號和選修的課程數量:

SELECT Sno,COUNT(*)

FROM SC

GROUP BY Sno                                 /*對查詢結果按Sno的值分組*/

HAVING COUNT(*) > 3;                     /*分組後 * 會依次表示不同的組,COUNT函式再計算每個組的元組數*/


 

連線查詢

連線查詢是指同時涉及兩個以上的表的查詢。

 

1. 等值與非等值連線查詢

連線查詢中的WHERE子句是由連線條件選擇條件組成的複合條件。執行復合條件的連線查詢時是先執行選擇條件,再執行連線條件,這是一種高效執行過程。

連線條件是指用比較運算子連線兩個表中的某個屬性列,連線條件中的列名稱為連線欄位,欄位的型別必須是可比的,但名字不必相同。如果屬性列名稱不同,可以省略表名字首。

當連線條件中的比較運算子為“=”時,連線稱為等值連線,否則稱為非等值連線

有時等值連線後會出現重複列,將重複列取消的連線稱為自然連線(可回顧“專門的關係運算”)

 

//查詢每個學生及其選修課程的情況:

<等值連線>

SELECT Student.*,SC.                          /*Student.*和SC.*表示兩表中的所有屬性列,所以表中會出現重複列*/

FROM Student,SC

WHERE Student.Sno = SC.Sno;

<自然連線>

SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade          /*雖然表中不會出現重複列,但是程式碼變長了_(:з」∠)_*/

FROM Student,SC

WHERE Student.Sno = SC.Sno;

 

//查詢選修2號課程,且成績在90分以上的所有學生姓名和學號:

<與選擇條件複合>

SELECT Student.Sno,Sname

FROM Student,SC

WHERE Student.Sno = SC.Sno               /*連線條件*/

               AND Cno = '2'                             /*選擇條件*/

               AND Grade >= 90;

 

2. 自身連線

連線操作不僅可以在不同表上進行,還可以是表與自身的連線。

為了實現自身連線,同時保證邏輯的正確性,我們需要給表起若干個別名

//查詢每一門選修課(Cno)的先修課程(Cpno):

<思路>選修課和先修課同位於表Course,我們需要起兩個別名,分別表示選修課和先修課所在的表。

SELECT FIRST.Cno,SECOND.Cpno                     /*FIRST表是選修課所在表,SECOND表是先修課所在表*/

FROM Course FIRST,Course SECOND                /*給Course起兩個別名*/

WHERE FIRST.Cpno = SECOND.Cno;                 /*在FIRST表中找先修課,再在SECOND表中找課程號*/

 

3. 外連線

採用外連線可以使被捨棄的懸浮元組顯示在查詢結果中。(可回顧“專門的關係運算”)

//查詢每個學生及其選修課的情況

SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade

FROM Student LEFT OUTER JOIN SC USING(Sno);           /*USING(Sno)等價於Student.Sno = SC.Sno*/

 

細心的同學可以注意到,外連線裡我們沒有用到WHERE子句來連線兩個表,這是因為例子中使用了JOIN短語FROM子句中使用JOIN就可以實現連線,而且

FROM Student INNER JOIN SC ON Student.Sno = SC.Sno; 等價於 WHERE Student.Sno = SC.Sno;

NATURAL JOIN 表示自然連線

LEFT OUTER JOIN 表示左外連線(同理可推導右外連線)

 

連線查詢時使用WHERE還是JOIN就根據大家的程式設計習慣而定嘍。

 

4. 多表連線

//查詢每個學生的姓名、選修課程及成績:

SELECT Sname,Cno,Grade

FROM Student,Course,SC

WHERE Student.Sno = SC.Sno AND Course.Cno = SC.Cno;


 

巢狀查詢

在SQL語言中,我們司空見慣的SELECT-FROM-WHERE語句被稱為一個查詢塊。將一個查詢塊巢狀在另一個查詢塊的WHERE子句HAVING短語中的查詢,我們定義為巢狀查詢

巢狀查詢使使用者可以用多個簡單的查詢構成複雜的查詢,增強SQL的查詢能力,這種查詢正是SQL(結構化查詢語言)中“結構化”的含義所在。

 

1.帶有IN謂詞的子查詢

在巢狀查詢中,子查詢的結果往往是一個集合,IN正是我們之前提到的“確定集合”的查詢謂詞。

//查詢選修了課程名為“資訊系統”的學生學號和名字:

<巢狀查詢>

SELECT Sno,Sname

FROM Student

WHERE Sno IN                                /*得到學生學號*/

             (SELECT Sno

               FROM SC                          /*用SC表將Student表和Course表聯絡起來

               WHERE Cno IN                 /*得到課程號*/

                            (SELECT Cno

                              FROM Course

                              WHERE Cname = '資訊系統'

                             )

              );

 

<連線查詢>

*有些巢狀查詢可以用連線替代,有些卻不可以。但在實際應用中,能夠用連線查詢的儘量用連線查詢!

SELECT Student.Sno,Sname

FROM Student,Course,SC

WHERE Student.Sno = SC.Sno

             AND SC.Cno = Course.Cno

             AND Course.Cname = '資訊系統';

 

*在上面的巢狀查詢中,子查詢的查詢條件不依賴於父查詢,子查詢的查詢結果用於建立父查詢的查詢條件,這樣的查詢叫做不相關子查詢。下面要介紹的巢狀查詢都屬於相關子查詢,即子查詢的查詢條件依賴於父查詢。求解相關子查詢不能像求解不相關子查詢那樣一次將子查詢解出來,然後求解父查詢。由於子查詢的查詢條件與父查詢相關,所以需要反覆求值。

 

2. 帶有比較運算子的子查詢

這種查詢用比較運算子來連線父查詢和子查詢,即用比較運算子取代IN短語

//找出每個學生超過自己選修課平均成績的課程號:

SELECT Sno,Cno

FROM SC x                              /*給SC起兩個別名*/

WHERE Grade >= 

             (SELECT AVG(Grade)

               FROM SC y

               WHERE x.Sno = y.Sno

             );

 

3. 帶有ANY(SOME)或ALL謂詞的子查詢

子查詢返回單值時可以用比較運算子,但返回多值時要加上ANY(有的系統用SOME)或ALL謂詞修飾符。

短語 語義
>ANY

大於最小值

<ANY 小於最大值
>ALL 大於最大值
<ALL 小於最小值

//查詢非計算機科學系中比計算機科學系任意一個學生年齡小的學生姓名和年齡:

/*要注意,這裡的“任意一個”並不是我們日常理解的“所有”。前面說過相關子查詢的巢狀查詢時反覆求值的,因此這裡的“任意一個”是指每一次求值時的“所有”,題幹要表達“所有”的時候一般會說“全部”。但其實用詞也沒那麼死板.......具體問題具體分析吧。*/

<使用ANY謂詞>

SELECT Sname,Sage

FROM Student

WHERE Sage <ANY

             (SELECT Sage

               FROM Student

               WHERE Sdept = 'CS')

AND Sdept != 'CS';                    /*不等於也可以用<>,其實從敲鍵盤的角度來說,<>還舒服一點*/

                                                    /*而且要注意,最後一行AND語句是父查詢的選擇條件之一*/

 

<使用聚集函式>

*在實際應用中,用聚集函式的查詢效率更高!

SELECT Sname,Sage

FROM Student

WHERE Sage <

             (SELECT MAX(Sage)

               FROM Student

               WHERE Sdept = 'CS')

AND Sdept != 'CS';     

 

4. 帶有EXISTS謂詞的子查詢

EXISTS謂詞只關心子查詢有無返回值,有為真,無為假,而不需要查詢具體值,所以有時候EXISTS是高效的查詢方法。

WHERE子句返回,則在父查詢中將正在被查詢的元組對應屬性值放入結果表,然後再查詢下一個元組。若返回,則跳過。

(1)存在量詞EXISTS

//查詢所有選修了1號課程的學生姓名:

SELECT Sname

FROM Student

WHERE EXISTS

              (SELECT *    /*由EXISTS引出的子查詢,目標列表達式通常用 * ,因為EXISTS只關心有無返回值,給列名無實際意義*/

                FROM SC

                WHERE Sno = Student.Sno AND Cno = '1'

               );

//查詢沒有選修1號課程的學生姓名

<NOT EXISTS>

...

WHERE NOT EXISTS

...                                  /*其餘部分不用變*/

 

*遇到否定句式的問題時,先轉換為肯定句式來寫出程式碼,再新增NOT,這樣就清晰很多了。

 

(2)全稱量詞NOT EXISTS-NOT EXISTS

SQL不提供全稱量詞,但我們可以用離散數學的知識,用EXISTS表示出來,即NOT EXISTS-NOT EXISTS(沒有不...的)

//查詢選修了全部課程的學生姓名:

<思路> 沒有一門是他選修的 => 存在課程他有選修的

第一步:從Student表中選擇一名學生開始查詢

SELECT Sname

FROM Student

WHERE 

第二步:存在一門課程:

EXISTS

             (SELECT *

              FROM Course

              WHERE 

第三步:存在正在查詢的學生選修的課程:

EXISTS

            (SELECT *

             FROM SC

             WHERE Sno = Student.Sno         /*在SC表中對比正在查詢的學生*/ 

              AND Cno = Course.Cno

            )

第四步:作思路的逆轉換,存在課程他有選修的 => 沒有一門是他選修的,即在EXISTS前加上NOT,並將前面的三部分連線起來:

SELECT Sname

FROM Student

WHERE NOT EXISTS

             (SELECT *

              FROM Course

              WHERE NOT EXISTS

                           (SELECT *

                            FROM SC

                            WHERE Sno = Student.Sno  

                            AND Cno = Course.Cno

                            )

               );                                                      /*恭喜,答案出來了!*/   

 

(3)蘊含(→)

SQL亦不提供蘊含的邏輯運算,但我們可以運用離散數學的知識表示出來,p→q ≡ 非p∨q ,即NOT EXISTS-OR。(但是在實際問題中,往往還需要進行若干次轉化)

//查詢至少選修了學生20170330選修的全部課程的學生學號:

<思路> 問題轉換。學生20170330選修了課程y,記為事件;其餘學生x選修了課程y,記為事件q。所以問題轉換為,對於所有課程y,p都能推出(蘊含)q 。更進一步推導,只用存在或不存在來描述,不存在課程y,學生20170330選修了,而且不存在於學生x的選修課中。

從上面的推論可以得出,我們需要三個SC表(使用別名,一個用來選擇學生x,一個用來確定學生20170330選擇的課程,一個用來建立兩者新的聯絡。

第一步:從SC表中選擇一名學生開始查詢,因為學生和他相應的課程資訊都在該表中:

SELECT Sname

FROM SC X

WHERE

第二步:存在學生20170330選修了的課程y:

EXISTS

           (SELECT *

             FROM SC Y

             WHERE Sno = '20170330'

第三步:而且存在學生x沒有選修課程y:

AND EXISTS

           (SELECT *

             FROM SC Z

             WHERE Z.Sno = X.Sno

             AND Z.Cno = Y.Cno

            )

第四步:反轉,在EXISTS前加上NOT連線上面的三個部分:

SELECT Sname

FROM SC X

WHERE NOT EXISTS

            (SELECT *

             FROM SC Y

             WHERE Y.Sno = '20170330' 

             AND NOT EXISTS

                    (SELECT *

                     FROM SC Z

                    WHERE Z.Sno = X.Sno

                    AND Z.Cno = Y.Cno

                    )

             );                                                /*恭喜,答案出來了!*/   


 

集合查詢

每一個查詢塊的查詢結果都是元組的集合,而對於集合,我們可以進行並(UNION)交(INTERSECT)差(EXCEPT)操作。(可回顧“傳統的集合運算”)

需要注意的是,參加集合操作的查詢結果必須列數相同,而且對應項的資料型別也必須相同

使用形式:

 

(查詢塊1)

[ UNION | INTERSECT | EXCEPT]

(查詢塊2)


 

基於派生表的查詢

子查詢不僅可以如巢狀查詢那樣,出現在WHERE子句中,它還可以出現在FROM子句中,這時子查詢又被稱為臨時派生表

//找出每個學生超過自己的選修課平均成績的課程號:

SELECT

FROM SC,( SELECT Sno,Avg(Grade)

                    FROM SC

                    GROUP BY Sno)

                    AS Avg_sc(Avg_sno,Avg_grade)            /*AS短語用於給臨時表命名,並指明屬性列*/

WHERE SC.Sno = Avg_sc.Avg_sno AND SC.Grade >= Avg_sc.Avg_grade;

/*如果子查詢沒有使用如AVG()這樣的聚集函式,派生表可以不指明屬性列,因為屬性列沒有被改變。*/


 

路過的圈毛君:“這篇《資料查詢》是我忙裡偷閒花了三天才爆肝出來的,有木有感覺超級良心呀~希望能給大家對資料查詢的理解帶來幫助_(:з」∠)_”