基本 SQL 之增刪改查(二)

上一篇文章中,我們介紹了 SQL 中最基本的 DML 語法,包括 insert 的插入數據、update 的更新數據、delete 的刪除數據以及基本的查詢語法,但大多比較簡單不能解決我們日常項目中複雜的需求。

那麼,本篇就來看一看較爲複雜繁瑣的查詢語法,例如多表的連接查詢、嵌套的子查詢以及很多預定的功能函數。

連接查詢

關係型數據庫的一個特點就是,多張表之間存在關係,以致於我們可以連接多張表進行查詢操作,所以連接查詢會是關係型數據庫中最常見的操作。

連接查詢主要分爲三種,交叉連接、內連接和外連接,我們一個個說。

1、交叉連接

交叉連接其實連接查詢的第一個階段,它簡單表現爲兩張表的笛卡爾積形式,具體例子:

image

如果你沒學過數學中的笛卡爾積概念,你可以這樣簡單的理解這裏的交叉連接:

兩張表的交叉連接就是一個連接合並的過程,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。

除此之外,還有一種全外連接,這種模式下沒有以誰爲基準,兩邊表的所有行都得出現。我們舉個例子:

image

兩邊的表都在看,自己哪些行成功的條件匹配了,哪些沒有,沒有成功匹配的行會在最後強制出現,未匹配的字段賦值爲 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 爲指定的格式,取值如下:

image

image

舉個例子:

select date_format(now(),'年:%Y-月:%m-日:%d');

執行 SQL,將輸出:

年:2019-月:02-日:25

雖然很奇怪的輸出格式,但我要說明的是這種可定製化的日期格式輸出函數。

數學函數:

  • ABS(column_name):求絕對值
  • PI():返回常量 3.141593
  • 還有一些正弦、餘弦,正切,平方根等函數

除此之外,還有很多函數,例如一些操作字符串、文本等等函數,這裏不一一贅述了,用到的時候嘗試性的搜一搜,看有沒有預定義的函數能夠解決你的當下需求。

那麼,繁雜的一篇又結束了,自認爲乾貨還是很足的,着重講了比較重要的連接查詢和子查詢,不知道你有啥想法呢,歡迎你和我討論!

<center>關注公衆不迷路,一個愛分享的程序員。</center> <center>公衆號回覆「1024」加作者微信一起探討學習!</center> <center>每篇文章用到的所有案例代碼素材都會上傳我個人 github</center> <center>https://github.com/SingleYam/overview_java</center> <center>歡迎來踩!</center>

YangAM 公衆號

原文出處:https://www.cnblogs.com/yangming1996/p/10435262.html

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