上一篇文章中,我們介紹了 SQL 中最基本的 DML 語法,包括 insert 的插入資料、update 的更新資料、delete 的刪除資料以及基本的查詢語法,但大多比較簡單不能解決我們日常專案中複雜的需求。
那麼,本篇就來看一看較為複雜繁瑣的查詢語法,例如多表的連線查詢、巢狀的子查詢以及很多預定的功能函式。
連線查詢
關係型資料庫的一個特點就是,多張表之間存在關係,以致於我們可以連線多張表進行查詢操作,所以連線查詢會是關係型資料庫中最常見的操作。
連線查詢主要分為三種,交叉連線、內連線和外連線,我們一個個說。
1、交叉連線
交叉連線其實連線查詢的第一個階段,它簡單表現為兩張表的笛卡爾積形式,具體例子:
如果你沒學過數學中的笛卡爾積概念,你可以這樣簡單的理解這裡的交叉連線:
兩張表的交叉連線就是一個連線合並的過程,T1 表中的每一行會分別與 T2 表的每一行做一個組合連線並單獨生成一行。例如 T1 的第一行會與 T2 的第一行合併生成一行,會與 T2 的第二行再合併生成一行,T2 的第三行合併生成一行,接著再以 T1 的第二行為基礎重複上述動作。
應該不難理解,我們看實現交叉連線的語法是什麼樣的。
交叉連線使用關鍵字 CROSS JOIN 進行連線,例如:
select * from table1 cross join table2
複製程式碼
也可以按照 ANSI SQL:1989 規範中指定的,使用逗號進行交叉連線,例如:
select * from table1,table2
複製程式碼
通過交叉連線,我們可以兩張表的資料進行一個結合,但是你會發現同時也產生了很多冗餘的垃圾資料行,所以我們往往也會結合 where 子句對結果集進行一個條件篩選。
例如我們有這麼兩張表:
Students表:
+----+--------+------+----------+
| id | name | age | departId |
+----+--------+------+----------+
| 1 | 小明 | 12 | 1 |
| 2 | 胖虎 | 13 | 4 |
| 3 | 小新 | 15 | 2 |
| 4 | 曹操 | 1000 | 3 |
| 5 | 王安石 | 2000 | 5 |
| 6 | 杜甫 | 2000 | 2 |
+----+--------+------+----------+
複製程式碼
departs 表:
+----+----------------+
| id | depart |
+----+----------------+
| 1 | 資訊工程學院 |
| 2 | 文學院 |
| 3 | 化工學院 |
| 4 | 外國語學院 |
| 5 | 數學與統計學院 |
+----+----------------+
複製程式碼
如果我們現在需要查詢一個學生的學院資訊,我們就需要連線兩個表,而我們的交叉連線會為我們產生太多冗餘資料行,我們可以使用 where 子句對笛卡爾積後的結果集進行一個條件篩選。
select * from students,departs
where students.departId = departs.id
複製程式碼
這樣就過濾了那些冗餘的垃圾資料行,得到我們需要的有效資料。
+----+--------+------+----------+----+----------------+
| id | name | age | departId | id | depart |
+----+--------+------+----------+----+----------------+
| 1 | 小明 | 12 | 1 | 1 | 資訊工程學院 |
| 3 | 小新 | 15 | 2 | 2 | 文學院 |
| 6 | 杜甫 | 2000 | 2 | 2 | 文學院 |
| 4 | 曹操 | 1000 | 3 | 3 | 化工學院 |
| 2 | 胖虎 | 13 | 4 | 4 | 外國語學院 |
| 5 | 王安石 | 2000 | 5 | 5 | 數學與統計學院 |
+----+--------+------+----------+----+----------------+
複製程式碼
但是我們仍然需要說一下,雖然交叉連線允許結合 where 子句過濾冗餘資料,但是笛卡爾積本身就造成了很大的資源消耗,對於這種情況,內連線會有更好效率體現,同樣能實現該需求。
總歸一句,笛卡爾積式的交叉連線謹慎使用。
2、內連線
內連線也可以理解為條件連線,它使用關鍵字 INNER JOIN 連線兩張表並使用 ON 篩選器篩選組合合適的資料行。基本語法如下:
select * from table1 inner join table2 on [條件]
我們同樣以上述的 students 和 departs 表舉例子,看這條 SQL:
select * from students
inner join departs
on students.departId = departs.id;
複製程式碼
會得到同樣的結果:
+----+--------+------+----------+----+----------------+
| id | name | age | departId | id | depart |
+----+--------+------+----------+----+----------------+
| 1 | 小明 | 12 | 1 | 1 | 資訊工程學院 |
| 3 | 小新 | 15 | 2 | 2 | 文學院 |
| 6 | 杜甫 | 2000 | 2 | 2 | 文學院 |
| 4 | 曹操 | 1000 | 3 | 3 | 化工學院 |
| 2 | 胖虎 | 13 | 4 | 4 | 外國語學院 |
| 5 | 王安石 | 2000 | 5 | 5 | 數學與統計學院 |
+----+--------+------+----------+----+----------------+
複製程式碼
我們也說了,雖然交叉連線也實現同樣的效果,但實現原理是不同的,效率也是不一樣的,交叉連線通過笛卡爾積返回結果集再結合 where 子句剔除冗餘資料行,而內連線的 ON 篩選器工作在笛卡爾積過程中,只有符合條件才能合併生成新的資料行。
這兩者的效率是不同的,內連線的效率顯然是大於等於交叉連線的,所以我們也建議了儘量使用內連線取代交叉連線的使用。
3、外連線
我們說內連線主要是兩個步驟的結合,笛卡爾積加 ON 篩選器,一般情況下也是內連線使用的最為頻繁。外連線其實是基於內連線的兩個步驟,額外新增了另一個步驟,進一步苛刻化查詢操作,我們依然以上述的學生表和學院表營造這麼一個場景:
我們的學生表中儲存了學生的個人資訊以及所屬的學院外來鍵編號,假設其中有部分學生的學院還未分配,即為NULL。
那麼我現在需要查詢出所有的學生及其所屬部門資訊,包括那些未知學院資訊的學生,請問你怎麼做?
這個問題的核心點在於,我不僅要滿足連線條件成功合併的資料行,還要那些未成功匹配的行,也就是說學生表的所有行都得出現。
不賣關子了,使用左外連線即可實現:
我們該一下 students 表中資料,departs 表中資料不變:
+----+------+------+----------+
| id | name | age | departId |
+----+------+------+----------+
| 1 | 小明 | 12 | 1 |
| 2 | 胖虎 | 13 | NULL |
| 3 | 小新 | 15 | 2 |
| 4 | 李白 | 200 | NULL |
+----+------+------+----------+
複製程式碼
我們執行 SQL :
select * from
students left outer join departs
on students.departId = departs.id;
複製程式碼
得到結果:
+----+------+------+----------+------+--------------+
| id | name | age | departId | id | depart |
+----+------+------+----------+------+--------------+
| 1 | 小明 | 12 | 1 | 1 | 資訊工程學院 |
| 2 | 胖虎 | 13 | NULL | NULL | NULL |
| 3 | 小新 | 15 | 2 | 2 | 文學院 |
| 4 | 李白 | 200 | NULL | NULL | NULL |
+----+------+------+----------+------+--------------+
複製程式碼
你看,左連線相當於以左表為基準,成功連線匹配的就列出其對應的學院資訊,不能匹配的就填充為 NULL。
當然,如果你想以右表為基準,你可以使用右連線,關鍵詞 right outer join/on。
除此之外,還有一種全外連線,這種模式下沒有以誰為基準,兩邊表的所有行都得出現。我們舉個例子:
兩邊的表都在看,自己哪些行成功的條件匹配了,哪些沒有,沒有成功匹配的行會在最後強制出現,未匹配的欄位賦值為 NULL。
這就是外連線的本質,希望你理解了。
子查詢
子查詢,顧名思義就是巢狀的別的查詢語句中的查詢,因為很多時候查詢不是一蹴而就的,往往是需要一箇中間結果集作一個過渡的,而我們的子查詢就是用於這種中間結果集過渡。
1、出現在選擇列表中
這種情況並不多見,但也是一種使用場景,可以應用在 insert,update,delete 和 select 語句中,我們分別來看。
insert:
insert into students
values(5,'yang',(select avg(id) from departs),1);
複製程式碼
我們向 students 表中插入一條資料,age 欄位的值為 departs 表 id 欄位的平均值,這個例子本身沒多大意義,但是我為你演示的是語法。
update:
update students set age=(select avg(id) from departs)
where students.id = 1;
複製程式碼
記住,子查詢一定要使用小括號括起來,強調一種優先順序,不然會與外查詢產生衝突報錯。
delete:
delete from students
where id = (select avg(id) from departs)
複製程式碼
select 語句的子查詢類似,不再贅述了。再次強調下,這種模式下使用子查詢並不多見,不要刻意為了裝逼寫成這樣,你會被打的!
2、含有in和exists的子查詢
in 這個關鍵字相信大家也不陌生,我們之前介紹 select 基本查詢的時候介紹過,它強調的是一種集合的概念,可以視作一種邏輯運算子。
比如:
select * from students
where id in(2,3,4,5,6)
複製程式碼
我們換成子查詢就變成:
select * from students
where id in(select id from departs)
複製程式碼
這時的子查詢返回的是一個集合,而不再是一個常量。
exists 帶來的子查詢主要思路:將主查詢的資料,放到子查詢中做條件驗證,根據驗證結果(TRUE 或 FALSE)來決定主查詢的資料結果是否得以保留。
只要子查詢有返回行,即返回 true,否則返回 false。
基本語法如下:
select [select_list] from [table]
where [not] exists [子查詢]
複製程式碼
看起來挺複雜,我們舉個例子你就明白了:
以我們的學生表來說,每個學生都儲存了一個學院欄位資訊,通過這個欄位的值可以具體查到學院的名字,那麼假如現學生表中有大量陳腐資料,無效或為空的學院 id 對應的資料都是有些有問題的資料,現需要查出這些資料
departs 表如下:
+----+----------------+
| id | depart |
+----+----------------+
| 1 | 資訊工程學院 |
| 2 | 文學院 |
| 3 | 化工學院 |
| 4 | 外國語學院 |
| 5 | 數學與統計學院 |
+----+----------------+
複製程式碼
students 表如下:
+----+------+------+----------+
| id | name | age | departId |
+----+------+------+----------+
| 1 | 小明 | 12 | 1 |
| 2 | 小紅 | 13 | 2 |
| 3 | 王菲 | 14 | 10 |
| 4 | 張三 | 23 | NULL |
+----+------+------+----------+
複製程式碼
顯然,學生表中的第三第四行資料有問題,他們的學院 id 非法,我們需要使用 SQL 找到這兩條資料。
select * from students
where not exists
(select * from departs where students.departId = departs.id)
複製程式碼
執行 SQL,看看結果:
+----+------+------+----------+
| id | name | age | departId |
+----+------+------+----------+
| 3 | 王菲 | 14 | 10 |
| 4 | 張三 | 23 | NULL |
+----+------+------+----------+
複製程式碼
首先外查詢拿到 students 表的所有資料,where 子句會遍歷每一行,執行子查詢過濾篩選,如果整個 where 子句返回為 true,代表該行記錄有效應該被查詢出來,否則將拋棄該行,繼續遍歷。
關於子查詢暫時句介紹到這,基本核心的內容都已經附帶例子的介紹了,什麼巢狀子查詢不過是多套了一層而已,相信你能夠理解,這裡不再贅述了。
常用函式
SQL 規範中定義了很多的函式方法,它們按照應用場景不同可以劃分為以下類別,聚合函式、日期時間函式、字串函式,邏輯函式等等,我們抽取幾個較為頻繁使用的方法介紹下,其餘的留待大家自行研究。
聚合函式:
- AVG(column_name):求取結果集某一列的平均值,如果某條記錄行該欄位NULL,將不參與平均值計算
- COUNT(column_name):統計結果集中資料記錄行數,即統計有多少條資料
- MAX(column_name):返回結果集中某列的最大值
- SUM(colunmn_name):求和結果集某列
- MIN(column_name):返回結果集中某列的最小值
- 除此之外,還有一些求標準差,方差
聚合函式的一個特點是,它往往是對整個結果集進行了某種數學運算並返回一個常量數值而非集合。
日期時間函式:
由於日期函式在不同的資料實現來說,很多相同功能的函式具有不同的函式名稱,我們這裡僅以 MySql 來介紹這些函式,給你一個巨集觀印象,不同的資料庫之間只不過語法差異,搜尋引擎就可以解決。
- NOW():返回當前日期時間,精確到時分秒
- CURDATE():返回當前的日期
- CURTIME():返回當前的時間
- DATA(日期時間/日期表示式):該函式用於提取引數中的日期部分,引數可為一個時分秒的日期物件,或字串表示式
- EXTRACT(unit FROM date):取出日期時間中指定部分,unit 可取值年、月、日、時分秒等等
- DATE_ADD(date,INTERVAL expr type):為指定引數的時間日期新增一個時間間隔
- DATEDIFF(date1,date2):返回兩個日期時間之間相隔的天數
- DATE_FORMAT(date,format):按照指定格式輸出指定的日期時間
我們詳細說一下最後一個函式,這個函式用於格式化輸出一個日期時間物件,format 為指定的格式,取值如下:
舉個例子:
select date_format(now(),'年:%Y-月:%m-日:%d');
執行 SQL,將輸出:
年:2019-月:02-日:25
雖然很奇怪的輸出格式,但我要說明的是這種可定製化的日期格式輸出函式。
數學函式:
- ABS(column_name):求絕對值
- PI():返回常量 3.141593
- 還有一些正弦、餘弦,正切,平方根等函式
除此之外,還有很多函式,例如一些操作字串、文字等等函式,這裡不一一贅述了,用到的時候嘗試性的搜一搜,看有沒有預定義的函式能夠解決你的當下需求。