SQL語言是一門相對來說簡單易學卻又功能強大的語言,它能讓你快速上手並很快就能寫出比較復雜的查詢語句。但是對於大多數開發者來說,使用SQL語句查詢數據庫的時候,如果沒有一個抽象的過程和一個合理的步驟,很可能會在寫一些特定的SQL查詢語句來解決特定問題的時候被卡住。
這里主要講述下SQL查詢的一些基本理論,以及寫查詢語句的抽象思路。
SQL查詢的簡介
SQL語言起源於1970年E.J.Codd發表的關系數據庫理論,所以可以說SQL是為關系數據庫服務的。而對於SQL查詢,是指從數據庫中取得數據的子集。
子集指的就是從一個或多個表中提取特定的字段和字段對應的記錄。
可以說,SQL中無論多復雜的查詢,都可以抽象成如上面的過程。
精確查詢的前置條件
想要正確取得所需要的數據子集,除了需要思路正確並將思路正確轉變為對應SQL查詢語句之外,還有很重要的一點是需要數據庫有着良好的設計。這里的良好設計指的是數據庫的設計符合業務邏輯。如果數據庫設計得很爛,那數據查詢起來就很困難,要達到精確查詢也就變得困難起來。
舉個簡單的例子,A表中有a字段,B表中也有a字段,在業務處理的過程中,A表中的a字段可能和B表中的a字段發生了分歧,這時候是要取A表中的a字段的值還是取B表中的a字段的值,也就成了一個問題。
兩種方式,同一種結果
在SQL中,取得相同的數據子集可以用不同的思路或不同的SQL語句,因為SQL源於關系數據庫理論,而關系數據庫理論又源於數學,思考如何構建查詢語句時,都可以抽象為兩種方法。
關系代數法
關系代數法的思路是對數據庫進行分步操作,最后取得想要的結果。
SELECT NAME FROM USERS WHERE AGE > 20;
關系代數的思路描述上面語句為:對USERS表進行投影(選擇列)操作,然后對結果進行篩選,只取得年齡大於20的結果。
關系演算法
相比較關系代數法而言,關系演算法更多關注的是取得數據所滿足的條件。比如上面SQL用關系演算法可以被描述為:我想得到所有年齡大於20的員工的姓名,部門和年齡。
兩種算法的區別
對於簡單的查詢語句來說,上面兩種方法都不需要,因為可能用腳就可以想出來了,只是問題在於很多情況下查詢語句會非常復雜,這時候就用兩種算法去區分描述的優勢才會表現出來。
對於關系演算法來說,更多關注的是所取出信息所滿足的條件;而對於關系代數法來說,更多關注的是如何取出特定的信息。簡單地說,關系演算法表示的是【what】,而關系代數法表達的是【how】。SQL語句中所體現的思路,有些時候是關系代數法,有些時候是關系演算法,還有些會是兩種思路的混合。
對於某些查詢情況,關系代數法可能會更簡單;而對於另外一些情況,關系演算法則會顯得更直接;還有一些情況則是需要混合兩種思路。所以這兩種思維方式在寫SQL查詢時都是必須要掌握的。
單表查詢
單表查詢是所有查詢中的中間狀態,即使是多個表的復雜查詢,在進行各種連接后最終都能夠被抽象成單表查詢。所以先從單表查詢開始。
選擇列的子集
根據上面數據子集的說法,選擇列是通過在SELECT語句后面添加所要選擇的列名實現的。
SELECT NAME FROM USERS;
選擇行的子集
選擇行的子集,是在SQL語句的WHERE子句后面加上相應的限制條件。當WHERE子句后面的表達式為【真/true】時,也就是滿足所謂的【條件】時,返回相應的行的子集。
WHERE子句后面的運算符分為兩類,分別是比較運算符和邏輯運算符。
比較運算符是將兩個相同類型的數據進行比較,進而返回布爾類型(boolean)的運算符。在SQL中,比較運算符一共有六種,分別是等於(=)、小於(<)、小於或等於(<=)、大於或等於(>=)以及不等於(<>)。其中,小於或等於和大於或等於可以看成是比較運算符和邏輯運算符的結合體。
SELECT NAME FROM USERS WHERE AGE >= 18;
邏輯運算符是將兩個布爾類型進行連接,並返回一個新的布爾類型的運算符。在SQL中,邏輯運算符通常是將比較運算符返回的布爾類型相連接以最終確定WHERE子句后面滿足條件的真假。邏輯運算符一共有三種,分別是與(AND)、或(OR)和非(NOT)。其中,非運算符可以看作是特殊的比較運算符。
SELECT NAME FROM USERS WHERE AGE > 18 AND AGE < 60;
另外,這幾種運算符是有優先級的,優先級由大到小排列,是比較運算符>於邏輯運算符(AND)>或邏輯運算符(OR)。當然,也可以通過括號運算符來改變優先級,括號運算符的優先級最高。
1+1≠2的問題
假定USERS表中存在一個SEX(性別)字段,我們一般可能會認為,這個世界不是男人就是女人,因此SEX字段就只能有male和female兩種值。
SELECT * FROM USERS WHERE SEX = 'male' AND SEX = 'female';
但是當我們執行上面的SQL時發現獲取的並不是USERS表中的所有記錄。這是因為,在實際的場景中,有一些用戶在錄入表單的時候可能不會填寫SEX字段,因此SEX字段還會有第三個值:NULL。因此我們需要加入SQL語句中提供的NULL判斷條件來獲取表中的所有記錄。
SELECT * FROM USERS WHERE SEX = 'male' AND SEX = 'female' AND SEX IS NULL;
當然了,實際場景中SEX(性別)一般會被設計為布爾類型,並提供默認值。
排序結果
上面的那些方法都是關於取出數據,而下面是關於將取出的子集進行排序。SQL通過ORDER BY子句來進行排序,ORDER BY子句必須是SQL查詢語句的最后一個子句,也就是說,在ORDER BY子句之后,不允許再添加任何的子句了。
ORDER BY子句分為升序(ASC)和降序(DESC)。如果不指定升序或者降序,則默認為升序(由小到大),而ORDER BY是根據排序依據的數據類型來決定的排列的先后順序的,分別有3種數據類型可以進行排序:字符、數字和時間日期。其中,字符按照字母表進行排序,數字根據數字大小排序,時間日期根據時間的先后進行排序。
SELECT * FROM USERS ORDER BY AGE DESC;
上面的語句即按AGE(年齡)對USERS表中的記錄進行降序排序。
聚合函數
聚合的意思就是按照一定的條件進行分組,SQL通過GROUP BY子句來進行分組,通過分組並配合聚合函數來達到聚合統計的目的。另外還提供有HAVING子句與GROUP BY子句聯合使用,作用是分組后對數據的篩選/過濾,優先級低於WHERE子句。
SELECT AREA, AVG(AGE) FROM USERS GROUP BY AREA HAVING AVG(AGE) > 18;
上面的語句即按AREA(地區)對USERS表中的記錄求平均AGE(年齡),且平均年齡大於18歲,簡單描述,就是求USERS表中平均年齡大於18歲的地區。
多表連接查詢
在關系數據庫中,一個查詢往往會涉及多個表,因為很少有數據庫只有一個表,而如果大多數查詢只涉及到一個表的話,那么那個表也往往低於第三范式,存在大量冗余和異常。因此,連接(JOIN)就是一種把多個表連接成一個表的重要手段。
笛卡爾積
笛卡爾積在SQL中的實現方式即是交叉連接(CROSS JOIN)。所有連接方式都會先生成臨時笛卡爾積表,笛卡爾積是關系代數里的一個概念,表示兩個表中的每一行數據任意組合。在實際應用中,笛卡爾積本身大多沒有什么實際用處,只有在兩個表連接時加上限制條件,才會有實際意義。
內連接
如果分步驟理解的話,內連接可以看做是先對兩個表進行了交叉連接后,再通過加上限制條件(SQL中通過關鍵字ON)剔除不符合條件的行的子集,得到的結果就是內連接了。
SELECT * FROM A INNER JOIN B ON A.ID = B.ID;
外連接
外連接是以一個表作為主體,通過限制條件(關鍵字ON)來進行連接。
左外連接(LEFT OUTER JOIN)是將左表作為主體。
SELECT * FROM A LEFT JOIN B ON A.ID = B.ID;
右外連接(RIGHT OUTER JOIN)是將右表作為主體。
SELECT * FROM A RIGHT JOIN B ON A.ID = B.ID;
全外連接(FULL OUTER JOIN)是將左表和右表每行都至少輸出一次,可以看作是左外連接和右外連接的結合。
SELECT * FROM A FULL JOIN B ON A.ID = B.ID;
從上面的語句可以看出,使用外連接編寫SQL時可以省略OUTER關鍵字。
自連接
如果說內連接是取兩個表之間的交集,那么自連接也可以這么理解(如果有WHERE,沒有WHERE的話則是取的兩個表所有記錄的完整笛卡爾積)。通常理解上是,自連接是一種特殊的內連接,不同在於內連接是使用ON關鍵字做限制條件,自連接則是通過WHETE子句來限制記錄的獲取。
SELECT * FROM A, B WHERE A.ID = B.ID;
交叉連接
交叉連接和沒有WHERE條件限制的自連接獲取的記錄結果是一樣的,它們都返回兩個表所有記錄的完整笛卡爾積。
SELECT * FROM A CROSS JOIN B;
多表連接查詢的總結
上面說的連接都是兩個表進行連接,而多個表連接實際上可以看成是對N個表進行N-1次雙表連接;多個表連接查詢之后也可以看作是一個整體,即看成對一個單表進行查詢操作。這樣分析的話,會讓復雜的問題簡單化,再困難的問題也能迎刃而解。
"假如人生是一場未知目的地的旅行,我們只是一味地狂奔,卻忘記了旅行的意義。"