引言
SQL語言是一門簡單易學卻又功能強大的語言,它能讓你快速上手並寫出比較複雜的查詢語句。但對於大多數開發人員來說,使用SQL查詢資料庫並沒有一個抽象的過程和一個合理的步驟,這很可能會使在寫一些特定的SQL查詢語句來解決特定問題時被”卡”住,本系列文章主要講述SQL查詢時一些基本的理論,以及寫查詢語句的抽象思路。
SQL查詢簡介
SQL語言起源於1970年E.J.Codd發表的關聯式資料庫理論,所以SQL是為關聯式資料庫服務的。而對於SQL查詢,是指從資料庫中取得資料的子集,這句話貌似聽著有些晦澀是吧,下面通過幾張圖片簡單說明一下:
假如一個資料庫中只有一個表,再假如所有資料如(取自AdventureWork樣本資料庫):
而對於子集的概念,look:
最後,子集如下:
其實,SQL中無論多複雜的查詢,都可以抽象成如上面的過程.
精確查詢的前置條件
對於正確取得所需要的資料子集.除了需要思路正確並將思路正確轉變為對應SQL查詢語句之外。還有很重要的一點是需要資料庫有著良好的設計.這裡的良好設計我所指的是資料庫的設計符合商務邏輯並至少實現第三範式,對於實現第三範式,這隻是我個人觀點,對於範式的簡單介紹,請看我的部落格:資料庫範式那些事.如果資料庫設計很糟糕,存在很多冗餘,資料庫中資訊存在大量異常,則即使SQL寫的正確,也無法取得精確的結果。
兩種方式,同一種結果
在SQL中,取得相同的資料子集可以用不同的思路或不同的SQL語句,因為SQL源於關聯式資料庫理論,而關聯式資料庫理論又源於數學,思考如何構建查詢語句時,都可以抽象為兩種方法:
1.關係代數法
關係代數法的思路是對資料庫進行分步操作,最後取得想要的結果.
比如如下語句:
Select Name,Department,AgeFrom Employeewhere Age>20
關係代數的思路描述上面語句為:對錶Employee表進行投影(選擇列)操作,然後對結果進行篩選,只取得年齡大於20的結果.
2.關係演算法
相比較關係代數法而言,關係演算法更多關注的是取得資料所滿足的條件.上面SQL可以用關係演算法被描述為:我想得到所有年齡大於20的員工的姓名,部門和年齡。
為什麼需要兩種方法
對於簡單的查詢語句來說,上面兩種方法都不需要.用腳就可以想出來了。問題在於很多查詢語句都會非常複雜。對於關係演算法來說更多的是關注的是所取出資訊所滿足的條件,而對於關係代數法來說,更多關注的是如何取出特定的資訊.簡單的說,關係演算法表示的是”what”,而關係代數法表達的是”how”.SQL語句中所透漏的思路,有些時候是關係代數法,有些時候是關係演算法,還有些是兩種思路的混合.
對於某些查詢情況,關係代數法可能會更簡單,而對於另外一些情況,關係演算法則會顯得更直接.還有一些情況.我們需要混合兩種思路。所以這兩種思維方式在寫SQL查詢時都是必須的.
單表查詢
單表查詢是所有查詢的中間狀態,既是多個表的複雜查詢在最終進行這種串連後都能夠被抽象成單表查詢。所以先從單表查詢開始。
選擇列的子集
根據上面資料子集的說法,選擇列是通過在select語句後面添加所要選擇的列名實現的:
比如下面資料庫中通過在select後面選擇相應的列名實現選擇列的子集.
相應sql語句如下:
SELECT [Name] ,[GroupName] FROM [AdventureWorks].[HumanResources].[Department]
選擇行的子集
選擇行的子集是在Sql語句的where子句後面加上相應的限制條件,當where子句後面的運算式為“真”時,也就是滿足所謂的“條件”時,相應的行的子集被返回。
where子句後面的運算子分為兩類,分別是比較子和邏輯運算子.
比較子是將兩個相同類型的資料進行比較,進而返回布爾類型(bool)的運算子,在SQL中,比較子一共有六種,分別為等於(=),小於(<),大於(>),小於或等於(<=),大於或等於(>=)以及不等於(<>),其中小於或等於和大於或等於可以看成是比較子和邏輯運算子的結合體。
而邏輯運算子是將兩個布爾類型進行串連,並返回一個新的布爾類型的運算子,在SQL中,邏輯運算子通常是將比較子返回的布爾類型相串連以最終確定where子句後面滿足條件的真假。邏輯運算子一種有三種,與(AND),或(OR),非(NOT).
比如上面,我想選擇第二條和第六條,為了說明比較子和邏輯運算子,可以使用如下Sql語句:
SELECT [Name] ,[GroupName] FROM [AdventureWorks].[HumanResources].[Department] WHERE DepartmentID>1 and DepartmentID<3 or DepartmentID>5 and DepartmentID<7
由此我們可以看出,這幾種運算子是有優先順序的,優先順序由大到小排列是比較子>於(And)>非(Or)
當然,運算子也可以通過小括弧來改變優先順序,對於上面那個表
對於不加括弧時:
SELECT * FROM [AdventureWorks].[HumanResources].[Department] WHERE DepartmentID>=1 and DepartmentID<=3 and DepartmentID>=5 or DepartmentID<=7
加了括弧改變運算順序後:
SELECT * FROM [AdventureWorks].[HumanResources].[Department] WHERE DepartmentID>=1 and DepartmentID<=3 and (DepartmentID>=5 or DepartmentID<=7)
很特別的NULL
假如在一個使用者註冊的表中,一些選填資訊並不需要使用者必須填寫,則在資料庫中儲存為null,這些null值在利用上面where子句後的運算子時,有可能造成資料丟失,比如一個選填資訊是性別(Gender),假設下面兩條條件子句:
where Gender="M"
where NOT (Gender="M")
由於null值的存在,這兩條語句返回的資料行加起來並不是整個表中的所有資料。所以,當將null值考慮在內時,where後面的條件子句擁有可能的值從真和假,增加為真,假,以及未知(null)。這些是我們在現實世界中想一些問題的時候可能的答案--真的,假的,我不知道。
所以我們如何在這種情況下不遺失資料呢,對於上面的例子來說,如何才能讓整個表的資料不被丟失呢,這裡必須將除了“真”,“假”以外的“未知”這個選項包含在內,SQL提供了IS NULL來表明未知這個選項:
where Gender IS NULL
將上面語句加入進去,則不會再遺失資料。
排序結果
上面的那些方法都是關於取出資料,而下面是關於將取出的子集進行排序。SQL通過Order by子句來進行排序,Order by子句是Sql查詢語句的最後一個子句,也就是說Order by子句之後不能再加任何的子句了。
Order By子句分為升序(ASC)和降序(DESC),如果不指定升序或者降序,則預設為升序(由小到大),而Order by是根據排序依據的資料類型決定,分別為3種資料類型可以進行排序:
- 字元
- 數字
- 時間日期
字元按照字母表進行排序,數字根據數字大小排序,時間日期根據時間的先後進行排序。
其它一些有關的
視圖
視圖可以看作是一個儲存的虛擬表,也可以簡單看做是儲存的一個查詢語句。視圖的好處是視圖可以根據視圖所查詢表的內容的改變而改變,打個比方來理解這句話是:
使用視圖的優點是可以對查詢進行加密以及便於管理,據說還可以最佳化效能(我不認可這點).
防止重複
有時候我們對於取出的資料子集不想重複,比如你想知道一些特定的員工一共屬於幾個部門
SELECT [EmployeeID] ,[DepartmentID] FROM [AdventureWorks].[HumanResources].[EmployeeDepartmentHistory]
這樣的結果是沒有意義的,SQL提供了Distinct關鍵字來實現這點:
SELECT distinct DepartmentID FROM [AdventureWorks].[HumanResources].[EmployeeDepartmentHistory]
彙總函式
所謂彙總函式,是為了一些特定目的,將同一列多個值彙總為一個,比如我想知道一群人中最大年齡是多少可以利用MAX(Age),比如我想知道一個班級平均測驗成績是多少可以用AVG(Result)……
總結
文章簡單概述了SQL查詢的原理以及簡單的單表查詢,這些都是資料庫查詢的基礎概念,對於進行複雜查詢來說,弄明白這些概念是必不可少的。