做者簡介html
多肉,餓了麼資深python工程師。曾在17年擔任餓了麼即時配送衆包系統的研發經理,這篇文章最先的版本就誕生於那段時間,目前負責配送相關業務系統的總體穩定性建設。我的比較喜歡c和python,最近有點迷rust,同時仍是個archlinux的平常用戶,有相似愛好的歡迎交流python
爲何以《寫給運營同窗和初學者的Sql入門教程》爲題?linux
這本來是給一位關係要好的運營同窗定製的Sql教程。在餓了麼,總部運營的同窗在排查、跟蹤線上問題和作運營決策的時候,除了經過運營管理系統查詢信息和依賴數據分析師給出的分析數據,經常也須要直接從數據庫管理臺經過寫Sql的方式獲取更細緻、更實時的業務數據,並基於這些數據進行一些及時的分析,從而更快的給出運營方案。在這樣的背景下,Sql已經愈來愈成爲咱們運營同窗的一項必備技能。網上有不少Sql教程(e.g. w3school),我也翻閱過一些運營同窗桌上的Sql紙質書,這些教程都很好,但廣泛側重介紹語法,不少不少的語法,配以簡短的demo。做爲老司機的reference book很贊,但是對於剛入門甚至尚未入門的學習者,就未免困難了一點。再回顧運營同窗的使用場景,大多數狀況下是根據一些已有條件作一些簡單的查詢,偶爾會有一些相對複雜的查詢,好比對查詢結果作一些聚合、分組、排序,或是同時查詢兩三張數據表,除此之外,建表、建索引、修改表字段、修改字段值等等這些操做,在運營同窗的平常工做中基本是不會遇到的。git
基於以上種種緣由,寫了這篇教程,初衷是可以幫助這位好朋友以更高的ROI入門Sql。下面是寫這篇教程時的一些考量:github
建議全部閱讀教程的同窗,都嘗試搭建一套本身的數據庫服務(建議安裝MySQL),對教程中的demo多作一些練習,不管是示例、小測驗仍是小溫習裏面的Sql語句,都不妨親自執行一下,這也是一種很好的幫助你熟悉語法的方式。固然搭建本身的數據庫會是一個不小的挑戰,寫做這篇教程的時候,我在本身的VPS上安裝了MySQL(MariaDB)並提供了一個鏈接腳本(隱藏了鏈接MySQL的過程)給朋友使用,可是這種方式並不適合推廣到全部人。具體的安裝和使用方式,不在本教程的敘述範圍內,因此...運營妹子們能夠求助下熟悉的研發同窗,漢子們嘛..算法
能夠從這裏sql_tutorial下載經過pandoc+latex導出的pdf,得到更好的閱讀體驗。sql
B.T.W數據庫
由餓了麼技術社區主辦的首屆物流技術開放日終於來啦!編程
時間:2018年12月30日後端
地點:餓了麼上海總部:普陀區近鐵城市廣場北座5樓榴蓮酥
這次活動邀請到了物流團隊的6位重量級嘉賓。不只會有先後端大佬分享最新的架構、算法在物流團隊的落地實戰經驗,更有 P10 大佬教你如何在業務開發中得到技術成長。固然,也會有各類技術書籍,記念品拿到手軟,最後最重要的一點,徹底免費!還等什麼,趕快點擊 etech.ele.me/salon.html?… 瞭解更多細節並報名吧!
👆我也會在此次開放日活動中分享Gunicorn有關的話題,歡迎你們報名參加。放出一張分享內容的Outline:
by 多肉
其實Sql並無那麼難。Sql是幫助你和關係型數據庫交互的一套語法,主要支持的操做有4類:
聽起來挺嚇人的對吧,但實際上DML、DDL、DCL這3類操做在平常的運營工做中幾乎都不會用到,常常會使用到的吶實際上是第一種,也就是數據查詢操做(DQL)。Sql基本的查詢語法也比較簡單,那麼難在哪裏呢?我猜測難在學習了基本語法以後,不知道怎麼應用到實際的Case上。在接下來的內容裏,我將以一些十分接近現實的衆包運營Case爲例,逐一解釋最基本的Sql查詢語法而且分析如何將它應用到具體的場景上。
好的吧,吹了一波牛仍是逃不過須要介紹一些最基礎的東西,可是我保證這是整篇教程中最枯燥的部分,後面就會有趣不少。
爲了更簡單的理解這兩個概念以及他們之間的關係,能夠這麼類比:
騎手id
,而對應到數據表裏面可能就叫作rider_id
,Excel的表名可能叫騎手基本信息表
,而對應到數據表的表名則可能叫tb_rider
;衆包業務
的文件夾,而後將衆包運營相關的Excel表格都放在這個文件夾裏面。數據庫也是如此,好比咱們會有一個庫名叫作crowd
的數據庫,裏面可能存放了tb_rider
、tb_order
等和騎手、運單相關的多張數據表;因此,「關係型數據庫」的概念很嚇唬人,但其實道理很簡單,就是列和列之間有必定的聯繫,整合在一塊兒就是一條有意義的數據,將這些數據概括起來就構成了一張表,而將一批有關聯的表一同管理起來就獲得了一個數據庫。
最基本的Sql查詢語法其實就一個:
SELECT 列名(或者*,表示全部列) FROM 表名 WHERE 篩選條件;
複製代碼
B.T.W 注意
SELECT...FROM...WHERE...;
語句結尾的這個分號,在標準Sql語法中這個分號是必要的
讓咱們按照FROM
、WHERE
、SELECT
的順序理解一下這個語法:
FROM 表名
:顧名思義,就是從表名指定的這張表格中;WHERE 篩選條件
:意思是「當知足篩選條件」的時候;SELECT 列名
:意思是選擇出這些記錄,而且展現指定的列名;串聯起來即是,從FROM後面指定的數據表中,篩選出知足WHERE後面指定條件的數據,而且展現SELECT後指定的這幾列字段。是否是很簡單吶?不過好像抽象了一點。因此咱們來看幾個具體的超簡單的例子。假設咱們有一張學生數學期末考試成績表,數據表長下面這樣,表名叫做tb_stu_math_score
。
id(自增主鍵) | name(學生姓名) | number(學號) | grade(年級) | class(班級) | score(得分) |
---|---|---|---|---|---|
1 | 柯南 | 010201 | 1 | 2 | 100 |
2 | 小哀 | 010202 | 1 | 2 | 100 |
3 | 光彥 | 010203 | 1 | 2 | 98 |
4 | 步美 | 010204 | 1 | 2 | 95 |
5 | 元太 | 010205 | 1 | 2 | 59 |
讓咱們試着理解一下下面幾個查詢語句:
[1] SELECT name FROM tb_stu_math_score WHERE score >= 95;
從tb_stu_math_score
表中挑選出得分大於95分的學生姓名,獲得的結果顯而易見:
name |
---|
柯南 |
小哀 |
光彥 |
步美 |
[2] SELECT name, number FROM tb_stu_math_score WHERE score < 60;
從tb_stu_math_score
表中挑選出得分小於60分的學生姓名,獲得的結果是:
name | number |
---|---|
元太 | 010205 |
[3] SELECT * FROM tb_stu_math_score WHERE score = 100;
從tb_stu_math_score
表中挑選出得分爲100分學生的全部信息(注意SELECT後面的*符號,表示全部字段),獲得的結果是:
id | name | number | grade | class | score |
---|---|---|---|---|---|
1 | 柯南 | 010201 | 1 | 2 | 100 |
2 | 小哀 | 010202 | 1 | 2 | 100 |
小測驗
看看下面這些Sql查詢語句你是否是知道是什麼含義而且知道查詢結果是什麼了呢?
1. SELECT name, grade, class, score FROM tb_stu_math_score WHERE number = "010201";
2. SELECT * FROM tb_stu_math_score WHERE name = "小哀";
3. SELECT id, score FROM tb_stu_math_score WHERE number = "010202";
複製代碼
剛剛咱們學習了Sql查詢的最最最最基礎的語法,可是相信我,全部的Sql查詢幾乎都長這個樣子,因此理解了這個最基礎的語法結構,後面學習起來就輕鬆多了。接下來讓我經過一些例子,擴展這個基礎語法,教你一些更加高級的Sql查詢操做。不過首先,咱們仍是要看一下接下來咱們的範例數據表長啥樣。
假設咱們有一張騎手數據表,表名叫做tb_rider
,還有一張運單數據表,表名叫做tb_order
,這兩張表分別長下面這個樣子。
[1] 騎手數據表:tb_rider
id | name | real_name_certify_state | level | level_city | is_deleted | created_at | updated_at |
---|---|---|---|---|---|---|---|
1 | Stark | 2 | 3 | 1 | 0 | 2017-01-01 22:00:19 | 2018-01-01 06:40:01 |
2 | Banner | 2 | 3 | 9 | 0 | 2017-04-28 12:01:19 | 2018-01-01 06:40:01 |
3 | Rogers | 2 | 2 | 1 | 0 | 2017-04-10 17:24:01 | 2018-01-01 06:40:01 |
4 | Thor | 1 | 0 | 1 | 0 | 2017-12-31 23:10:39 | 2018-01-01 06:40:01 |
5 | Natasha | 2 | 1 | 1 | 0 | 2017-02-11 15:03:13 | 2018-01-01 06:40:01 |
6 | Barton | 2 | 1 | 9 | 0 | 2017-02-11 15:04:19 | 2018-01-01 06:40:01 |
7 | Coulson | 2 | 3 | 9 | 0 | 2017-01-03 23:00:22 | 2018-01-01 06:40:01 |
8 | Coulson | 1 | 0 | 2 | 0 | 2017-01-05 10:10:23 | 2018-01-01 06:40:01 |
字段含義:
id
:自增主鍵。又是一個聽起來很嚇人的名字,但實際含義很簡單。「自增」的意思是,每次在這張數據表中建立一條新記錄的時候,數據庫都會在上一個id值的基礎上自動加上一個固定的步長(默認就是+1)做爲新記錄的id值。而所謂「主鍵」,就是可以在一張數據表中惟一標識一條記錄的字段,由於每條記錄的id都不同,因此它是主鍵。這個字段能夠是爲了單純的標識數據的惟一性,也能夠具備一些業務含義,好比這裏的id就同時也是騎手的帳號id;name
: 騎手姓名;real_name_certify_state
: 實名認證狀態:1-認證中,2-認證經過,3-認證失敗;level
:騎手等級,3-金牌,2-銀牌,1-銅牌,0-普通;level_city
:等級城市;is_deleted
:這條數據是否有效,在大多數產線相關的數據表中,都會有這樣的標記字段。0-未刪除(表示有效), 1-已刪除(表示無效);created_at
:這條數據的建立時間,也是全部產線數據表中都必須有的字段;updated_at
:這條數據最新一次被更新的時間,一樣是全部產線數據表中都必須有的字段;[2] 運單數據表:tb_order
id | order_id | order_state | rider_id | rider_name | grabbed_time | created_at | updated_at |
---|---|---|---|---|---|---|---|
1 | 300000201712300001 | 40 | 1 | Stark | 2017-12-30 12:34:55 | 2017-12-30 12:34:17 | 2017-12-30 12:39:30 |
2 | 300000201712300002 | 40 | 1 | Stark | 2017-12-30 12:34:56 | 2017-12-30 12:34:18 | 2017-12-30 12:44:27 |
3 | 300000201712300003 | 40 | 2 | Banner | 2017-12-30 13:23:12 | 2017-12-30 13:20:02 | 2017-12-30 13:54:09 |
4 | 300000201712300004 | 40 | 5 | Natasha | 2017-12-30 13:35:03 | 2017-12-30 13:34:19 | 2017-12-30 14:03:17 |
5 | 300000201712300005 | 40 | 1 | Stark | 2017-12-30 16:01:22 | 2017-12-30 16:01:03 | 2017-12-30 16:08:21 |
6 | 300000201712300006 | 40 | 3 | Rogers | 2017-12-30 16:10:45 | 2017-12-30 16:08:57 | 2017-12-30 16:34:27 |
7 | 300000201712310001 | 20 | 6 | Barton | 2017-12-31 09:12:57 | 2017-12-31 09:12:07 | 2017-12-31 09:20:35 |
8 | 300000201712310002 | 80 | 7 | Coulson | 2017-12-31 09:15:01 | 2017-12-31 09:10:33 | 2017-12-31 09:20:17 |
9 | 300000201712310003 | 80 | 2 | Banner | 2017-12-31 09:20:17 | 2017-12-31 09:18:10 | 2017-12-31 09:22:24 |
10 | 300000201712310004 | 20 | 3 | Rogers | 2017-12-31 10:37:33 | 2017-12-31 10:34:01 | 2017-12-31 10:38:09 |
11 | 300000201712310005 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:29:02 | 2017-12-31 19:29:02 | |
12 | 300000201712310006 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:29:27 | 2017-12-31 19:29:27 | |
13 | 300000201712310007 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:30:01 | 2017-12-31 19:30:01 |
字段含義:
id
:自增主鍵。吶,這裏的id就是單純的主鍵做用,沒有其餘的業務含義;order_id
:運單號,業務層面上運單的惟一標識;order_state
:運單當前的狀態。10-待搶單,20-待到店,80-待取餐,40-已送達;rider_id
:搶單的騎手id,還未被搶的運單這個字段是默認值0;rider_name
:搶單的騎手姓名,還未被搶的運單這個字段是默認值空字符;grabbed_time
:搶單時間,還未被搶的運單這個字段是默認的"1970-01-01 00:00:00"(這是一個特殊的時間,有興趣的話能夠搜索關鍵詞:時間戳);created_at
:這條數據的建立時間,也是全部產線數據表中都必須有的字段;updated_at
:這條數據最新一次被更新的時間,一樣是全部產線數據表中都必須有的字段;小溫習
試着理解看看下面這幾條Sql的含義以及返回的數據結果吧?
1. SELECT name, real_name_certify_state FROM tb_rider WHERE level = 3;
2. SELECT * FROM tb_order WHERE rider_id = 1;
3. SELECT rider_id, rider_name, order_id, grabbed_time FROM tb_order
WHERE order_state = 40;
複製代碼
場景: 線下反饋了一批騎手說本身理應是上海的金牌,可是牌級是普通或者展現的是金牌卻沒有享受到上海的金牌活動,你已經知道了這幾個分別是id=(2, 4, 7)的騎手,想排查一下他們的等級更新狀況。
這時你能夠選擇像這樣一條一條的查詢,像以前咱們介紹的那樣:
1. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id=2;
2. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id=4;
3. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id=7;
複製代碼
這樣固然能夠達到目的,可是隻有兩三個騎手的時候還勉強能夠操做,若是有幾十個騎手這樣查起來就太費勁了。這時候咱們能夠使用IN
這個語法。
SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id IN(2, 4, 7);
複製代碼
很簡單的對吧?但咱們仍是來簡單理解一下,WHERE id IN(2, 4, 7)
的意思就是篩選id字段的值在2,4,7這幾個值當中的記錄,執行這條Sql語句你就會獲得下面這樣的結果。
name | real_name_certify_state | level | level_city |
---|---|---|---|
Banner | 2 | 3 | 9 |
Thor | 1 | 0 | 1 |
Coulson | 2 | 3 | 9 |
因而你會發現,Thor這個騎手由於他沒有經過實名認證因此確定評不上金牌,Banner和Coulson兩位騎手雖然都是金牌騎手,可是等級城市倒是福州,因此享受不到上海金牌的活動。
那若是不知道騎手id,只知道騎手的名字怎麼辦?也能夠使用IN
查詢,只是這時候篩選的條件變成了name
,取值範圍也變成了"Banner", "Thor", "Coulson"。就像這樣。
SELECT name, real_name_certify_state, level, level_city FROM tb_rider
WHERE name IN("Banner", "Thor", "Coulson");
複製代碼
因而你順利的獲得瞭如下的結果。
name | real_name_certify_state | level | level_city |
---|---|---|---|
Banner | 2 | 3 | 9 |
Thor | 1 | 0 | 1 |
Coulson | 2 | 3 | 9 |
Coulson | 1 | 0 | 2 |
Oops! 竟然有兩個Coulson!
這就是在實際應用中要特別注意的地方了:
當你使用相似騎手id這種被設計爲惟一值的字段做爲查詢依據時,返回的結果也是惟一的,而當你使用相似騎手姓名這類字段做爲查詢依據時,就有可能出現上面這種狀況。這時候你就須要依賴更多的信息來判斷,哪一條纔是你真正想要的。因此可以用明確的字段做爲查詢依據時就要儘量的使用。
最經常使用的關係運算符有兩個AND
和OR
,用來鏈接多個篩選條件。顧名思義,AND
就是**「而且」的意思,也就是同時知足AND
先後兩個篩選條件;OR
就是「或者」**的意思,也就是知足OR
先後任何一個篩選條件。有點抽象了對不對,咱們看一個具體的例子。
場景: 假設你想要看看2017-02-01(包括2017-02-01當天)到2017-06-01(不包括2017-06-01當天)期間註冊的騎手全部信息。
註冊時間對應到數據上就是騎手信息的建立時間(created_at
),換句話說,就是查詢tb_rider``表中建立時間處於2017-02-01到2017-06-01之間的數據。那這樣的Sql應該怎麼寫呢,這時咱們就能夠用到
AND```。
SELECT * FROM tb_rider WHERE created_at >= "2017-02-01 00:00:00"
AND created_at < "2017-06-01 00:00:00";
複製代碼
B.T.W 注意由於包括2017-02-01當天,而不包括2017-06-01當天,因此前者是
>=
,然後者是<
。
讓咱們再來推廣一下。假設如今的場景變成:想看一看2017-02-01(包括當天)以前,或者2017-06-01(包括當天)以後註冊的騎手全部信息。咱們應該怎麼寫這個Sql呢?既然是或的關係,咱們就應該使用OR
了。
SELECT * FROM tb_rider WHERE created_at <= "2017-02-01 00:00:00"
OR created_at >= "2017-06-01 00:00:00";
複製代碼
B.T.W 注意這裏既包括了2017-02-01當天,又包括了2017-06-01當天,因此前者是
<=
,後者是>=
。
固然啦,AND
和OR
這樣的關係運算符,不只僅可以鏈接先後兩個篩選條件,也能夠經過使用若干個AND
和OR
鏈接多個不一樣的篩選條件。好比:想要看看2017-02-01(包括2017-02-01當天)到2017-06-01(不包括2017-06-01當天)期間註冊的且當前是金牌等級的騎手全部信息,那麼咱們能夠這麼寫。
SELECT * FROM tb_rider
WHERE created_at >= "2017-02-01 00:00:00"
AND created_at < "2017-06-01 00:00:00"
AND level = 3;
複製代碼
讓咱們先小小的複習一下上面學到的知識點,有一個這樣的場景:
咱們打算看一下Stark這位騎手,在2017-12-30當天搶單且當前狀態爲已完成的運單號和運單的建立時間。
如何寫這個Sql呢?先思考3s...1...2...3,看看是否和你想的同樣。
SELECT order_id, created_at FROM tb_order
WHERE rider_id = 1
AND grabbed_time >= "2017-12-30 00:00:00"
AND grabbed_time < "2017-12-31 00:00:00"
AND order_state = 40;
複製代碼
若是你沒有寫對,不要緊,讓咱們來分析一下:
rider_id = 1
;grabbed_time
這個字段,而2017-12-30當天,實際上指的就是2017-12-30 00:00:00(包括)到2017-12-31 00:00:00(不包括)這段時間;order_state
字段標識了運單狀態,所以咱們的篩選條件是order_state = 40
;執行這個語句,咱們獲得了下面這樣的結果。
order_id | created_at |
---|---|
300000201712300001 | 2017-12-30 12:34:17 |
300000201712300002 | 2017-12-30 12:34:18 |
300000201712300005 | 2017-12-30 16:01:03 |
有點美中不足,我想按照運單的建立時間倒序排序把最近建立的運單排在最前面,這時候就能夠使用ORDER BY
語法了。
SELECT order_id, created_at FROM tb_order
WHERE rider_id = 1
AND grabbed_time >= "2017-12-30 00:00:00"
AND grabbed_time < "2017-12-31 00:00:00"
AND order_state = 40
ORDER BY created_at DESC;
複製代碼
讓咱們再來理解一下,DESC
是**「遞減"**的意思,與之對應的是ASC
遞增。ORDER BY created_at DESC
的含義是,按照(BY)created_at
字段值遞減(DESC)的順序對查詢結果排序(ORDER)。因而咱們獲得以下的結果。
order_id | created_at |
---|---|
300000201712300005 | 2017-12-30 16:01:03 |
300000201712300002 | 2017-12-30 12:34:18 |
300000201712300001 | 2017-12-30 12:34:17 |
B.T.W 在現實場景中有時候查詢結果的集合會很大(例如幾百行、幾千行),可是咱們只想看其中前10行的數據,這時候咱們能夠使用LIMIT語法。例如這裏咱們能夠使用LIMIT語法僅僅展現前兩行查詢結果: SELECT order_id, created_at FROM tb_order WHERE rider_id = 1 AND grabbed_time >= "2017-12-30 00:00:00" AND grabbed_time < "2017-12-31 00:00:00" AND order_state = 40 ORDER BY created_at DESC LIMIT 2;
咱們再來看一個更加複雜的場景:假設想要查詢2017-12-30和2017-12-31兩天全部運單的全部信息,並先按照騎手id遞增,再按運單狀態遞減的順序排序展現。仍是先思考一下子。
這時的Sql相似長這樣。
SELECT * FROM tb_order
WHERE created_at >= "2017-12-30 00:00:00"
AND created_at < "2018-01-01 00:00:00"
ORDER BY rider_id ASC, order_state DESC;
複製代碼
若是前面的每一個知識點都理解了,這裏應該就只對**「先按照騎手id遞增,再按運單狀態遞減的順序排序展現」**有所疑惑。實際上咱們不只能夠對一個字段排序,還能夠把多個字段做爲排序的依據,並且不一樣字段上的排序規則(遞增/遞減)能夠不一樣。但排序是有優先級的,好比這裏,只有當rider_id
字段的值都相同沒法區分順序時,纔會對相同rider_id
的這幾條數據再按照order_state
字段的值進行排序。舉例來講,rider_id = 2
且order_state = 80
的數據,也依然不可能排在rider_id = 1
且order_state = 40
的數據前面。
執行這條Sql語句,將獲得的結果以下。
id | order_id | order_state | rider_id | rider_name | grabbed_time | created_at | updated_at |
---|---|---|---|---|---|---|---|
11 | 300000201712310005 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:29:02 | 2017-12-31 19:29:02 | |
12 | 300000201712310006 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:29:27 | 2017-12-31 19:29:27 | |
13 | 300000201712310007 | 10 | 0 | 1970-01-01 00:00:00 | 2017-12-31 19:30:01 | 2017-12-31 19:30:01 | |
1 | 300000201712300001 | 40 | 1 | Stark | 2017-12-30 12:34:55 | 2017-12-30 12:34:17 | 2017-12-30 12:39:30 |
2 | 300000201712300002 | 40 | 1 | Stark | 2017-12-30 12:34:56 | 2017-12-30 12:34:18 | 2017-12-30 12:44:27 |
5 | 300000201712300005 | 40 | 1 | Stark | 2017-12-30 16:01:22 | 2017-12-30 16:01:03 | 2017-12-30 16:08:21 |
9 | 300000201712310003 | 80 | 2 | Banner | 2017-12-31 09:20:17 | 2017-12-31 09:18:10 | 2017-12-31 09:22:24 |
3 | 300000201712300003 | 40 | 2 | Banner | 2017-12-30 13:23:12 | 2017-12-30 13:20:02 | 2017-12-30 13:54:09 |
6 | 300000201712300006 | 40 | 3 | Rogers | 2017-12-30 16:10:45 | 2017-12-30 16:08:57 | 2017-12-30 16:34:27 |
10 | 300000201712310004 | 20 | 3 | Rogers | 2017-12-31 10:37:33 | 2017-12-31 10:34:01 | 2017-12-31 10:38:09 |
4 | 300000201712300004 | 40 | 5 | Natasha | 2017-12-30 13:35:03 | 2017-12-30 13:34:19 | 2017-12-30 14:03:17 |
7 | 300000201712310001 | 20 | 6 | Barton | 2017-12-31 09:12:57 | 2017-12-31 09:12:07 | 2017-12-31 09:20:35 |
8 | 300000201712310002 | 80 | 7 | Coulson | 2017-12-31 09:15:01 | 2017-12-31 09:10:33 | 2017-12-31 09:20:17 |
這個部分相對有一點難,能夠多對比着例子理解一下。
進入到這個部分,說明以前的內容你基本都已經掌握了,在平常運營的操做中有30%左右的場景均可以使用前面講述的這些知識點解決(固然會有個熟能生巧的過程)。這個部分,我將繼續介紹幾個更加高級、固然也更加有難度的Sql技能,當你結束這一部分的學習而且熟練掌握這些技能的時候,你會發現絕大部分須要經過查數據來確認的場景你均可以勝任。由於這個章節的內容自己難度又大了些,若是再對着一張複雜的表就更加難以關注重點,所以咱們精簡一下表結構,只保留一些必要的字段。新的tb_order
表以下。
id | order_id | order_state | rider_id | rider_name | merchant_customer_distance | created_at |
---|---|---|---|---|---|---|
1 | 300000201712300001 | 40 | 1 | Stark | 2.5 | 2017-12-30 12:34:17 |
2 | 300000201712300002 | 40 | 1 | Stark | 1.8 | 2017-12-30 12:34:18 |
3 | 300000201712300003 | 40 | 2 | Banner | 1.8 | 2017-12-30 13:20:02 |
4 | 300000201712300004 | 40 | 5 | Natasha | 2.7 | 2017-12-30 13:34:19 |
5 | 300000201712300005 | 40 | 1 | Stark | 1.2 | 2017-12-30 16:01:03 |
6 | 300000201712300006 | 40 | 3 | Rogers | 0.5 | 2017-12-30 16:08:57 |
7 | 300000201712310001 | 20 | 6 | Barton | 1.3 | 2017-12-31 09:12:07 |
8 | 300000201712310002 | 80 | 7 | Coulson | 2.9 | 2017-12-31 09:10:33 |
9 | 300000201712310003 | 80 | 2 | Banner | 0.7 | 2017-12-31 09:18:10 |
10 | 300000201712310004 | 20 | 3 | Rogers | 2.2 | 2017-12-31 10:34:01 |
11 | 300000201712310005 | 10 | 0 | 0.3 | 2017-12-31 19:29:02 | |
12 | 300000201712310006 | 10 | 0 | 1.3 | 2017-12-31 19:29:27 | |
13 | 300000201712310007 | 10 | 0 | 3.0 | 2017-12-31 19:30:01 |
新增的列: merchant_customer_distance
:配送距離(商家到用戶的直線距離),單位是公里(km)。
千萬別被聚合函數這個名字嚇唬到,能夠簡單的理解爲對數據進行一些加工處理,讓咱們先來分別看一下這幾個聚合函數的基本定義。
COUNT
:對查詢結果集合中特定的列進行計數;SUM
:對查詢結果的某個字段進行求和;AVG
:就是average的意思,對查詢結果的某個字段計算平均值;讓咱們分別來看幾個具體的例子。
[1] 場景:查詢2017-12-30這一天,騎手Stark的全部完成單(狀態爲40)總量
你能夠這樣來寫這個Sql。
SELECT COUNT(id) FROM tb_order WHERE rider_id = 1
AND order_state = 40 AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00";
複製代碼
到這裏你應該已經可以很好的理解WHERE...AND...AND...
這部分的含義,咱們就再也不過多的討論這個部分(對本身要有信心!試着理解先本身理解一下)。
讓咱們重點來看一下COUNT(id)
這部分的含義。其實很簡單,就是對id
這一列進行計數。連起來看這段Sql,意思就是:從tb_order
這張表中(FROM tb_order
)篩選(WHERE
)騎手id爲1(rider_id = 1
)且運單狀態爲已完成(order_state = 40
)且建立時間大於等於2017年12月30日(created_at >= "2017-12-30 00:00:00
)且建立時間小於2017年12月31日(created_at < "2017-12-31 00:00:00
)的數據,而且按照id
這列對返回的結果集合進行計數。
咱們看到tb_order
這張表中,2017-12-30當天由騎手Stark配送且狀態是已完成的運單分別是30000020171230000一、30000020171230000二、300000201712300005這幾個運單號的運單,對應的自增id分別是id=[1, 2, 5],因此對id
這一列進行計數獲得的結果是3。因此咱們獲得的查詢結果以下表。
COUNT(id) |
---|
3 |
有時候你僅僅是想查一下知足某個條件的記錄的總行數,而並不是想對某個特定的列進行計數,這時就能夠使用COUNT(*)
語法。好比上面的這個Sql也能夠寫成下面這個樣子。
SELECT COUNT(*) FROM tb_order WHERE rider_id = 1
AND order_state = 40 AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00";
複製代碼
由於返回的結果有三行,因此咱們會獲得下表的結果。
COUNT(*) |
---|
3 |
看起來COUNT(列)
和COUNT(*)
是徹底等價的?有些特定的場景下的確如此,這裏須要補充一下COUNT的兩個小脾氣。
COUNT
不會自動去重;COUNT
在某一條查詢結果中,用來計數的那一列的值爲**「空"**時,這條記錄不進行計數;B.T.W 注意這裏的「空」指的是
<null>
,而不是某一列沒有展現出任何值就是空,這是一個相對技術的概念,當前不理解能夠先跳過
有一點暈是嗎?不着急,咱們來看兩個例子。假設有兩張表,很簡單的表,長下面這樣。
示例表1:tb_sample_1
id | name |
---|---|
1 | Stark |
2 | Stark |
3 | Coulson |
4 | Natasha |
5 | Stark |
示例表2:tb_sample_2
id | name |
---|---|
1 | Stark |
2 | Stark |
3 | <null> |
4 | <null> |
5 | Natasha |
6 | Coulson |
咱們下猜一猜下面幾條Sql的執行結果分別是什麼?
1. SELECT COUNT(id) FROM tb_sample_1;
2. SELECT COUNT(*) FROM tb_sample_1;
3. SELECT COUNT(name) FROM tb_sample_1;
4. SELECT COUNT(name) FROM tb_sample_2;
複製代碼
B.T.W 當
SELECT...FROM...WHERE...
語句中的WHERE...
部分被省略時,表示查詢表中的全部數據(不對數據進行篩選)。
讓咱們逐一分析一下。
1. SELECT COUNT(id) FROM tb_sample_1;
複製代碼
這條Sql沒有太多能夠分析的,由於tb_sample_1
表中id
字段的取值範圍是id=[1, 2, 3, 4, 5],共5個,因此咱們獲得的結果以下。
COUNT(id) |
---|
5 |
2. SELECT COUNT(*) FROM tb_sample_1;
複製代碼
這條Sql也沒有太多須要分析的,由於COUNT(*)
的含義是計算查詢結果的總行數,tb_sample_1
共5行數據,因此咱們獲得的結果以下。
COUNT(*) |
---|
5 |
3. SELECT COUNT(name) FROM tb_sample_1;
複製代碼
這條Sql裏面咱們對name
這一列進行計數,tb_sample_1
表中包含3個Stark,1個Coulson和1個Natasha,由於COUNT不進行自動去重,所以結果是5=3(Stark)+1(Coulson)+1(Natasha)
,以下表。
COUNT(name) |
---|
5 |
4. SELECT COUNT(name) FROM tb_sample_2;
複製代碼
這條Sql語句咱們仍是對name
這一列進行計數,tb_sample_2
表中包含2個Stark,1個Coulson,1個Natasha以及2個<null>
,因爲COUNT不去重所以2個Stark都會被計數,但COUNT不會對值爲**「空」**的結果進行計數,所以兩個<null>
都會被忽略。因此最終的結果爲4=2(Stark)+1(Coulson)+1(Natasha)
,以下表。
COUNT(name) |
---|
4 |
[2] 場景:查詢Stark這名騎手的累計配送里程
讓咱們先定義一下累計配送里程:騎手全部配送完成單的配送距離(商家到用戶的直線距離)之和。
這裏的關鍵詞是求和,因此咱們要用到SUM
這個聚合函數。對字段求和的意思是把返回的結果集合中該字段的值累加起來。讓咱們看下這個場景的Sql怎麼寫。
SELECT SUM(merchant_customer_distance) FROM tb_order
WHERE rider_id = 1 AND order_state = 40;
複製代碼
讓咱們來分析一下這條語句,FROM tb_order WHERE rider_id = 1 AND order_state = 40
已經比較好理解了,就是從tb_order
表中篩選出騎手id爲1且配送狀態爲40的記錄。而這裏的SUM(merchant_customer_distance)
的含義,就是對前面的條件篩選出的數據結果中的merchant_customer_distance
列的值進行求和。根據騎手id和配送狀態篩選出的記錄分別爲id=(1, 2, 5),對應的merchant_customer_distance
的值分別爲merchant_customer_distance=(2.5, 1.8, 1.2),求和結果爲5.5=2.5+1.8+1.2,以下表。
SUM(merchant_customer_distance) |
---|
5.5 |
[3] 場景:查詢Stark這名騎手的平均配送里程
一樣的,讓咱們先來定義一下平均配送里程:騎手全部完成單的配送距離(商家到用戶的直線距離)之和除以總的完成單量。
基於SUM
的經驗和前面的「預告」,不難想到此次咱們會用到AVG
這個聚合函數。對字段求平均值的意思是,把結果集合中該字段的值累加起來再除以結果總行數。AVG
幫咱們自動完成了「作除法」的動做,因此Sql的長相和上一個場景的SUM
是一模一樣的。
SELECT AVG(merchant_customer_distance) FROM tb_order
WHERE rider_id = 1 AND order_state = 40;
複製代碼
根據騎手id和配送狀態篩選出的記錄分別爲id=(1, 2, 5),對應的merchant_customer_distance
的值分別爲merchant_customer_distance=(2.5, 1.8, 1.2),求平均值的結果爲1.83=(2.5+1.8+1.2) / 3,以下表。
AVG(merchant_customer_distance) |
---|
1.83 |
寫在3.1節的最後:
對着這幾個場景學習下來,不知道你感受怎麼樣吖?是否以爲這幾個聚合函數自己還蠻簡單的,或者也有可能會以爲一會兒灌輸了不少知識點有點費勁呢?其實聚合函數有它複雜的一面,咱們上面看的這些Case都是比較簡單的使用方式。可是千萬不要擔憂,一方面是由於運營工做中遇到的絕大多數場景都不會比這些示例Case更復雜,另外一方面是不鼓勵過於複雜的使用這些聚合函數,由於查詢的邏輯越是複雜就越是難以「預測」查詢的結果,Sql並非一個適合表達「邏輯」的語言,若是對數據的再加工邏輯不少,就應該考慮像分析師提需求或者學習更加利於表達邏輯的其餘編程語言。
其次要說的就是多給本身些信心,同時也要多一點耐心。Sql雖然不一樣於Python、Java這樣的通用編成語言,除了語法還雜糅着一套體系化的編程概念、設計哲學,可是初次上手的時候仍是會感受到有些吃力的。可是隻要多去理解幾遍示例、多本身寫一寫,特別是在以後遇到實際工做中真實場景的時候本身思考如何轉化爲Sql、多實踐、多回顧分析,很快就會在潛移默化中掌握它,要相信熟能生巧。
接下來的3.二、3.3節,我會繼續介紹兩個實用的Sql語法,以及如何將它們和聚合函數結合使用,會更有難度一些。
DISTINCT語法顧名思義就是對某一列的值進行去重,讓咱們首先來回顧一下3.1節中COUNT的其中一個例子。
這個例子使用的是tb_sample_1
這張表,這張表很簡單,讓我再把它貼出來。
id | name |
---|---|
1 | Stark |
2 | Stark |
3 | Coulson |
4 | Natasha |
5 | Stark |
對應的,咱們想要回顧的這條Sql語句也很簡單。
SELECT COUNT(name) FROM tb_sample_1;
複製代碼
前面咱們已經分析過這條Sql:對name
這列進行計數,有3個Stark,1個Coulson,1個Natasha,因此獲得最終的結果以下表。
COUNT(name) |
---|
5 |
但是有的時候,咱們不想對相同的名字進行重複計數,當有多個相同的名字時只計數一次。這時候就能夠使用到DISTINCT語法。
SELECT COUNT(DISTINCT name) FROM tb_sample_1;
複製代碼
對比上一條Sql只是增長了一個DISTINCT關鍵字,其實理解起來呢也不用把它想的太複雜啦:COUNT(DISTINCT name)
就是對去重後的name
進行計數。tb_sample_1
中有3個Stark,可是3個Stark是重複的,使用DISTINCT語法後只會被計算一次,另外還有1個Coulson和一個Natasha,因此獲得的結果以下表。
COUNT(DISTINCT name) |
---|
3 |
DISTINCT語法能夠單獨使用,這時就是它自己的意思,對某列的值進行去重。可是相比之下,更常見的是像上面的例子同樣和COUNT這個聚合函數一塊兒使用,這樣就能夠對去重後的結果進行計數。
前面咱們基於tb_order
這張表講解了不少Sql的語法知識,讓咱們再來回憶一下這張表的容顏。
id | order_id | order_state | rider_id | rider_name | merchant_customer_distance | created_at |
---|---|---|---|---|---|---|
1 | 300000201712300001 | 40 | 1 | Stark | 2.5 | 2017-12-30 12:34:17 |
2 | 300000201712300002 | 40 | 1 | Stark | 1.8 | 2017-12-30 12:34:18 |
3 | 300000201712300003 | 40 | 2 | Banner | 1.8 | 2017-12-30 13:20:02 |
4 | 300000201712300004 | 40 | 5 | Natasha | 2.7 | 2017-12-30 13:34:19 |
5 | 300000201712300005 | 40 | 1 | Stark | 1.2 | 2017-12-30 16:01:03 |
6 | 300000201712300006 | 40 | 3 | Rogers | 0.5 | 2017-12-30 16:08:57 |
7 | 300000201712310001 | 20 | 6 | Barton | 1.3 | 2017-12-31 09:12:07 |
8 | 300000201712310002 | 80 | 7 | Coulson | 2.9 | 2017-12-31 09:10:33 |
9 | 300000201712310003 | 80 | 2 | Banner | 0.7 | 2017-12-31 09:18:10 |
10 | 300000201712310004 | 20 | 3 | Rogers | 2.2 | 2017-12-31 10:34:01 |
11 | 300000201712310005 | 10 | 0 | 0.3 | 2017-12-31 19:29:02 | |
12 | 300000201712310006 | 10 | 0 | 1.3 | 2017-12-31 19:29:27 | |
13 | 300000201712310007 | 10 | 0 | 3.0 | 2017-12-31 19:30:01 |
溫故而知新!先來出幾道題目複習一下前面所學的Sql知識。
複習題1: 試着寫出如下幾個場景對應的Sql語句
複習題2: 試着理解如下幾條Sql的含義而且寫出查詢的結果
1. SELECT COUNT(order_id) FROM tb_order WHERE order_state = 40
AND merchant_customer_distance >= 2.0 AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00";
2. SELECT AVG(merchant_customer_distance) FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00" AND created_at < "2017-12-31 00:00:00";
3. SELECT COUNT(DISTINCT rider_id) FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00" AND created_at < "2017-12-31 00:00:00";
複製代碼
聰明的你是否發現複習題2就是複習題1的答案呢?若是尚未發現,不要緊,再回過頭來多分析幾遍,Practice Makes Perfect 絕對是真理。不過複習這幾個例子可不只僅是爲了複習哦,讓咱們在一、2兩個場景的基礎下擴展一下,講解新的知識點。思考下面這兩個場景。
首先分析一下這裏的場景1。「2017-12-30當天」這個條件不難轉化爲created_at >= '2017-12-30 00:00:00' AND created_at < '2017-12-31 00:00:00'
,「完成單」不難轉化爲order_state = 40
,因爲要計算運單的「總量」咱們也不難想到能夠對order_id
進行COUNT操做。那麼如何分組到每一個騎手身上呢?這時候就要用到GROUP BY了。
SELECT COUNT(order_id) FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00" AND created_at < "2017-12-31 00:00:00"
GROUP BY rider_id;
複製代碼
注意這裏執行順序是先按照WHERE條件進行篩選,而後根據騎手id進行分組(GROUP BY),最後再對每一個分組按照運單號進行計數。所以咱們能夠獲得下表的結果。
COUNT(order_id) |
---|
3 |
1 |
1 |
1 |
好像有哪裏不對?結果中看不到對應的騎手吖!不着急,咱們稍微修改下剛纔的Sql,將騎手id、騎手姓名這2列展現出來就能夠了。
SELECT rider_id, rider_name, COUNT(order_id)
FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00"
GROUP BY rider_id;
複製代碼
咱們獲得以下表的結果。
rider_id | rider_name | COUNT(order_id) |
---|---|---|
1 | Stark | 3 |
2 | Banner | 1 |
5 | Natasha | 1 |
3 | Rogers | 1 |
這樣是否是就清晰多了。
再來分析場景2。有了前面的例子,「2017-12-30當天」、「完成單」這兩個條件應該是已經駕輕就熟、信手拈來了,「平均配送距離」問題也不大,能夠轉化爲AVG(merchant_customer_distance)
。那麼如何分組到每一個騎手身上呢?仍是經過GROUP BY
語法。咱們的Sql長成下面這個樣子。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00"
GROUP BY rider_id;
複製代碼
獲得以下表的結果。
rider_id | rider_name | AVG(merchant_customer_distance) |
---|---|---|
1 | Stark | 1.83 |
2 | Banner | 1.8 |
5 | Natasha | 2.7 |
3 | Rogers | 0.5 |
仍是須要特別提一下這裏的執行順序,首先執行的是WHERE
條件篩選,而後對篩選出的數據結果根據騎手id進行分組,最後再對每一個分組中的數據進行merchant_customer_distance
列的求平均值。
HAVING語法的含義相似於WHERE,當咱們使用HAVING的時候通常遵循HAVING 篩選條件
的語法結構。你可能會問啦,既然和WHERE語法含義差很少、使用方式又很相似,那幹嗎還要憑空多個HAVING語法出來呢?緣由就在於聚合函數。WHERE語法是不能和聚合函數一塊兒使用的,但有些時候咱們卻須要依賴聚合函數的計算結果做爲篩選條件。讓咱們看一下3.3節中場景2這個例子。
場景2:查詢2017-12-30當天每一個參與跑單騎手的完成單平均配送距離。
經過前面咱們的分析,獲得這樣的Sql。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00"
GROUP BY rider_id;
複製代碼
咱們在場景2的基礎上再擴展一下。
擴展的場景2:查詢2017-12-30當天每一個參與跑單騎手的完成單平均配送距離,並篩選出其中平均配送距離超過1.5km的數據。
咱們獲得這樣的Sql結果。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state = 40
AND created_at >= "2017-12-30 00:00:00"
AND created_at < "2017-12-31 00:00:00"
GROUP BY rider_id
HAVING AVG(merchant_customer_distance) > 1.5;
複製代碼
比較一下不難發現,變化僅僅是末尾多了HAVING AVG(merchant_customer_distance) > 1.5
這條子句。讓咱們分析看看。SELECT ... FROM ... WHERE ...
和以前的用法並無變化,GROUP BY rider_id
將SELECT的結果根據rider_id
進行分組,分組完成後HAVING AVG(merchant_customer_distance) > 1.5
語句對每一組的merchant_customer_distance
字段值求取平均數,而且將平均數大於1.5的結果篩選出來,做爲返回結果。
執行這條Sql咱們獲得結果。
rider_id | rider_name | AVG(merchant_customer_distance) |
---|---|---|
1 | Stark | 1.83 |
2 | Banner | 1.8 |
5 | Natasha | 2.7 |
Rogers這位騎手(騎手id=3)由於平均配送距離爲0.5,不知足HAVING語句指定的「平均配送距離大於1.5km」的篩選條件,因此沒有在咱們的查詢結果中。
類型這個詞此刻你聽起來可能仍是很陌生的,但其實在計算機科學領域,類型是一個很是基礎並且普遍存在的概念,幾乎每一種編程語言都有本身的類型系統。
B.T.W 在二進制中每個0或者1被稱做一個比特位,因此32位是指一段二進制數據中0和1的個數加在一塊兒共有32個,例如
00000000000000000000000000000001
表示一個32位二進制數,0000000000000001
表示一個16位二進制數。
[1] 爲何要定義類型的概念?
關於爲何要有類型這個概念,我吶有一個「不成熟」的理解:編程語言做爲人和機器交互的一種工具,人類對數據有人類邏輯上的理解,當咱們看到2903的時候咱們會認爲這是個整數,當咱們看到1031.2903的時候咱們會認爲這是個小數。而機器在處理數據或者存取數據的時候,是無差異的按照比特位進行二進制運算或者讀寫的。人類很難作到直接用二進制輸入計算機,固然也不能接受計算機直接以二進制的形式輸出結果。設想一下,若是某天我們想用一下電腦上的計算器,計算個1+1=2
,可是咱們沒有類型,咱們須要理解機器是如何處理二進制的,那麼就可能須要輸入00000000000000000000000000000001 + 00000000000000000000000000000001
,而獲得的結果也是二進制00000000000000000000000000000010
,這得多累人吶。有了類型就輕鬆多了,經過定義數據的類型,根據類型的約定,計算機就知道如何將這個1轉化爲二進制(包括:應該轉化爲16位、32位仍是64位的二進制,對這段二進制數據進行操做的時候,應該把它看做整數仍是浮點數等等),而返回結果的時候也就知道如何將二進制的00000000000000000000000000000010
轉化爲咱們可以理解的整數2
編程語言的類型其實就是人與機器約定好的,去理解和操做數據的一套規則。
總而言之,在機器的眼裏,不管是對數據進行何種操做,它看到的都是一串一串由0和1構成的東西,稱呼這種東西有專門的術語,叫做**「字節流」或者「二進制流「**。
讓咱們再一塊兒看一個例子。假設要處理這樣的一段二進制流:00000000100111011000001111010111
,這段二進制流能夠表示不少東西,要明確它的含義,就須要明確它的類型,好比下面這兩種不一樣的類型,這段流表示的內容就徹底不一樣。
0000000010011101
表示一個整型,後16位1000001111010111
表示一個整型),那麼它分別表明的是157和33751這兩個整數;我知道你此刻對爲什麼轉換爲32位整型是10322903?爲什麼看做2個16位整型轉換後是157和33751?還有着不少疑惑。可是關於二進制和十進制的轉換方法呢,在這裏就不作展開了,若是你很感興趣、很想知道能夠再單獨給你講這個方法。講上面的這些,最主要的仍是但願你明白,定義「類型」的概念,根本上是在人機交互的過程當中提供了一種機制,賦予無差異的二進制流必定的語義。
仍是太抽象了對不對?不要緊,咱們再來舉個栗子。
前面咱們在預備知識這一章中使用到了tb_stu_math_score
這張表,爲了避免讓你辛苦的再翻回去,咱們再貼一下這張表的內容啦。
id(自增主鍵) | name(學生姓名) | number(學號) | grade(年級) | class(班級) | score(得分) |
---|---|---|---|---|---|
1 | 柯南 | 010201 | 1 | 2 | 100 |
2 | 小哀 | 010202 | 1 | 2 | 100 |
3 | 光彥 | 010203 | 1 | 2 | 98 |
4 | 步美 | 010204 | 1 | 2 | 95 |
5 | 元太 | 010205 | 1 | 2 | 59 |
也寫過相似下面這條Sql語句。
SELECT score FROM tb_stu_math_score WHERE id=1;
複製代碼
這條Sql語句很是很是的簡單,如今咱們已經知道它會返回第一行數據score
這一列的值,結果長下面這樣。
score |
---|
100 |
讓咱們分析一下獲取這個結果的整個流程,幫助你理解一下,類型是如何發揮做用的。
score
這列的值;score
值是相似於00000000000000000000000001100100
這樣的二進制流;tb_stu_math_score
表的定義,score
這一列被定義爲整型,因而將二進制流轉化爲一個整型數,通過進制轉換獲得00000000000000000000000001100100
對應的整型值爲100,因而咱們看到的結果就是100。實際上反過來也很是相似,當咱們向這張表中寫入數據時,例如寫入的score
列的值爲100。由於存儲基於二進制,根據表的定義,score
列的類型爲整型,因而將值100按照整型轉換爲對應的二進制流00000000000000000000000001100100
,而且寫入到庫中。
[2] Sql的主要數據類型有哪些?
Sql中經常接觸的數據類型主要包括幾類。
1 整型
tinyint
:用來表示很小很小的整數,好比經常用它做爲is_deleted
、is_valid
這些字段的字段類型,由於這兩個字段表示該條記錄是否有效,只存在兩個值分別是0和1;smallint
:比tinyint
稍微大一點點的整型,能夠表示更大一點的整數,好比200、40四、401這樣的整數值;int
:經常使用的整型,能夠用來表示比較大的整數,好比10322(事實上int
能夠表示的整數範圍遠遠比這個大);bigint
:用來表示很是大的整數,好比大多數表的自增id就會使用這個類型,能夠表示相似10322903這樣很是大的整數(事實上bigint
能夠表示的整數範圍遠遠比這個要大);2 浮點型
decimal
:能夠表示很是準確的小數,好比經緯度;3 字符串類型
char
:固定長度的字符串;varchar
:可變長度的字符串;這裏固定長度和可變長度指的是數據庫中的存儲形式,由於這部分的內容其實有些超出了這個教程的範圍,咱們不過多的解釋這裏的區別。通常在咱們實際的應用中varchar
用的更多一些。它們都表示相似於"very glad to meet u, Huohuo!"
這樣的一串字符,固然也能夠是中文"敲開心認識你,火火!"
。
4 日期類型
date
:表示一個日期,只包含日期部分,不包含時間,好比當前日期"2018-01-23";datetime
:表示一個日期,同時包含日期部分和時間部分,好比當前日期"2018-01-23 03:01:43";咱們在這裏只是簡單的介紹了幾種Sql中常見的字段類型,並無很深刻的去解釋它們的原理、差別以及一些其餘的數據類型,我們不着急去學習那些「高大上」的內容,先理解這些類型的含義。
[3] 怎麼知道一張表中每一列的類型是什麼?
第1種方式是使用DESC 表名
命令,例如咱們想看一下以前提到的tb_rider
表的每一列字段類型,就能夠執行命令DESC tb_rider
,獲得下面的結果。
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
id | int(11) | NO | PRI | <null> | auto_increment |
name | varchar(32) | NO | |||
real_name_certify_state | int(11) | NO | 0 | ||
is_deleted | tinyint(4) | NO | 0 | ||
created_at | datetime | NO | MUL | CURRENT_TIMESTAMP | |
updated_at | datetime | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
level | tinyint(4) | NO | 0 | ||
level_city | varchar(32) | NO |
注意這裏的第一列表示字段名稱,第二列Type
則表示對應字段的字段類型。好比id
字段,是一個int
類型。
第二種方式是使用SHOW CREATE TABLE 表名
命令,例如SHOW CREATE TABLE tb_rider
,獲得下面的結果。
CREATE TABLE `tb_rider` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '姓名',
`real_name_certify_state` int(11) NOT NULL DEFAULT '0' COMMENT '身份證認證狀態',
`is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '該用戶是否還存在. 0: 不存在, 1: 存在',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
`level` tinyint(4) NOT NULL DEFAULT '0' COMMENT '騎手等級:0普通 1銅牌 2銀牌 3金牌',
`level_city` varchar(32) NOT NULL DEFAULT '' COMMENT '配送員等級城市',
PRIMARY KEY (`id`),
KEY `ix_created_at` (`created_at`),
KEY `ix_updated_at` (`updated_at`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='配送員信息';
複製代碼
咱們以
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '姓名'
複製代碼
來解釋一下這裏的語句。
name
是字段名(列名);varchar
表示字段類型爲字符串;NOT NULL
表示這個字段不能爲空, 爲空的意思是沒有指定任何值給這個字段(注意不等價於空字符串);COMMENT '姓名'
是對這個字段的備註,表示這個字段的業務含義,只用作展現;索引絕對算得上是關係型數據庫中最關鍵同時也是最有難度的話題。即使是經驗豐富的研發同窗,也常常會踩到索引的坑。不過咱們這裏介紹索引,只是爲了更好的服務於查詢,我會盡量避免牽扯進一些複雜的概念和底層原理。
[1] 什麼是索引?
那麼到底什麼是索引呢?你能夠把數據庫理解爲一本很厚的書(假設有10萬頁),書中的內容就是數據庫裏的數據,那麼索引就是書的目錄。 假設你歷來沒有閱讀過這本書,此刻你想要閱讀書的第7章第2小節。若是沒有目錄,你可能須要翻閱整本書找到你要閱讀的內容。可是在有目錄的狀況下,你就只須要先查一下目錄找到對應的頁碼,而後直接翻到那一頁就能看到你想看的內容了。索引也是相似的,首先查詢索引找到目標數據的位置,再從特定的位置讀取出數據的內容。
如何設計索引,是設計數據庫表的時候考慮的關鍵點之一。索引通常由表中的某一列或者某幾列構成,一旦設置某一列爲索引,那麼以後每次在往表中寫入數據的時候,都會更新這一列到索引中去。事實上,索引在技術層面是比較複雜的,涉及到磁盤I/O、B樹、優化器(Optimizer)等不少技術概念,不過咱們先不去深究這些。
[2] 爲何索引很重要,它有什麼用?
索引之因此重要,最主要的緣由是可以大大提升查詢的速度。上面咱們舉了書的例子,當這本書的頁數足夠大的時候(假設有2000萬頁),若是沒有目錄,想要查閱其中的某一章節的內容,那幾乎就是天方夜譚了。數據庫也是如此,當表中的數據只有幾行或者幾十行、幾百行的時候,有沒有索引其實差異不大,可是當表中的數據很是很是多的時候(好比衆包的運單表,2000萬+ 行),若是沒有索引,要找到某一條目標數據,查詢的速度就會很是很是很是的慢。
[3] 如何使用索引?
要使用索引很是簡單,只須要在WHERE
條件中使用到索引列做爲查詢條件,讓咱們舉個例子。
id | order_id | order_state | rider_id | rider_name | merchant_customer_distance | created_at |
---|---|---|---|---|---|---|
1 | 300000201712300001 | 40 | 1 | Stark | 2.5 | 2017-12-30 12:34:17 |
2 | 300000201712300002 | 40 | 1 | Stark | 1.8 | 2017-12-30 12:34:18 |
3 | 300000201712300003 | 40 | 2 | Banner | 1.8 | 2017-12-30 13:20:02 |
4 | 300000201712300004 | 40 | 5 | Natasha | 2.7 | 2017-12-30 13:34:19 |
5 | 300000201712300005 | 40 | 1 | Stark | 1.2 | 2017-12-30 16:01:03 |
6 | 300000201712300006 | 40 | 3 | Rogers | 0.5 | 2017-12-30 16:08:57 |
7 | 300000201712310001 | 20 | 6 | Barton | 1.3 | 2017-12-31 09:12:07 |
8 | 300000201712310002 | 80 | 7 | Coulson | 2.9 | 2017-12-31 09:10:33 |
9 | 300000201712310003 | 80 | 2 | Banner | 0.7 | 2017-12-31 09:18:10 |
10 | 300000201712310004 | 20 | 3 | Rogers | 2.2 | 2017-12-31 10:34:01 |
11 | 300000201712310005 | 10 | 0 | 0.3 | 2017-12-31 19:29:02 | |
12 | 300000201712310006 | 10 | 0 | 1.3 | 2017-12-31 19:29:27 | |
13 | 300000201712310007 | 10 | 0 | 3.0 | 2017-12-31 19:30:01 |
仍是這張tb_order
表,假設這張數據表中order_id
是索引列,那麼當咱們以order_id
做爲查詢條件時,咱們就利用了索引,好比下面這條Sql。
SELECT * FROM tb_order WHERE order_id = 300000201712310007;
複製代碼
固然啦,相似的使用order_id
做爲查詢條件的Sql也都會利用到索引,看看你是否都理解下面兩條Sql語句的含義。
1. SELECT * FROM tb_order
WHERE order_id IN (300000201712310007, 300000201712310006)
AND order_state = 40;
2. SELECT order_id, order_state FROM tb_order
WHERE order_id >= 300000201712300001
AND order_id <= 300000201712300006
AND order_state = 40;
複製代碼
那麼若是一張表裏面不止一列是索引,而在查詢的Sql中這些索引列都做爲了WHERE
語句的查詢條件,會使用哪一個列做爲索引仍是都使用?假設tb_order
表中order_id
和rider_id
兩列都是索引列,那麼下面這條Sql語句會使用哪一個做爲索引呢?
SELECT * FROM tb_order
WHERE order_id >= 300000201712310001
AND order_id <= 300000201712310007
AND rider_id > 0;
複製代碼
答案是不肯定的。使用哪一個索引,甚至是否使用索引,從根本上來講是由優化器(Optimizer)決定的,它會分析多個索引的優劣,以及使用索引和不使用索引的優劣,而後選擇最優的方式執行查詢。這部分話題就太過複雜了,這裏不作展開。儘管有優化器(Optimizer)的存在,可是對於咱們的查詢來講,可以使用明確的索引字段做爲查詢條件的,就應該儘量使用索引字段。
[4] 索引的類型、如何肯定表中的哪些列是索引列?
還記得字段類型一節中提到的DESC 表名
和SHOW CREATE TABLE 表名
語法嗎?前面咱們將這兩個語法用在了tb_rider
表上,這一節讓咱們看一看tb_order
表。
首先是DESC tb_order
,咱們會獲得下面的結果。
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
id | bigint(20) | NO | PRI | <null> | auto_increment |
order_id | bigint(20) | NO | UNI | 0 | |
rider_id | int(11) | NO | 0 | ||
rider_name | varchar(100) | NO | |||
order_state | tinyint(4) | NO | 0 | ||
is_deleted | tinyint(4) | NO | 0 | ||
grabbed_time | timestamp | NO | CURRENT_TIMESTAMP | ||
merchant_customer_distance | decimal(10,2) | NO | 0.00 | ||
created_at | datetime | NO | MUL | CURRENT_TIMESTAMP | |
updated_at | datetime | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
以前咱們關注的是Type
這一項,這裏讓咱們關注Key
這一項。咱們看到有些列對應的Key
是空的,這就表示這一列(或者叫這個字段)不是索引列(或者叫索引字段)。但id
、order_id
、created_at
和updated_at
這幾列對應的Key
均是有值的,這說明這幾列都是索引列。但這幾列Key
的值又各不相同,這是爲啥吶?這是之內索引也分爲不一樣的類型,讓咱們逐個來解釋一下。
PRI
:是primary的縮寫,標記這一列爲主鍵,主鍵的概念咱們在一開始的時候有介紹過,就是用來惟一標識表中每一行數據的索引;UNI
: 是unique的縮寫,顧名思義就是惟一的意思, 被設置爲UNI KEY
的列,不容許出現重複的值,若是嘗試向表中插入這一列的值徹底相同的兩行數據,則會引起報錯。我猜你確定會以爲疑惑,那UNI KEY
和PRI KEY
有啥區別?首先是這兩種類型的索引在實現上是有區別的(這一點我們不深究,涉及到了數據庫底層對索引的實現),其次PRI KEY
更多的是數據庫層面的語義,僅僅是描述數據的惟一性,而UNI KEY
則更可能是業務層面的語義,好比說這裏的order_id
字段,由於業務上不能存在兩個運單號徹底相同的運單,因此須要把order_id
這一列設置爲UNI KEY
;MUL
:是multiple的縮寫,表示這一列是被設置爲一個普通索引。之因此叫作multiple
,是由於此時可能這一列單獨做爲索引,也可能這一列和其餘標記爲MUL
的列共同構成了一個索引(這種由多列共同構成的索引被叫做複合索引);如今咱們還處在Sql以及數據庫知識(是的,除了Sql,我還偷偷介紹了一些數據庫原理)學習的初級階段,因此讓咱們知道這寫差別,可是不着急去把這些搞得一清二楚,它們都是索引,只要合理使用,均可以幫助咱們加快Sql查詢的效率。
另外一種識別表中索引列的方法就是經過SHOW CREATE TABLE 表名
命令,好比SHOW CREATE TABLE tb_order
,咱們獲得下面的結果。
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '對外不提供,內部使用',
`order_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '運單的跟蹤號(能夠對外提供)',
`rider_id` int(11) NOT NULL DEFAULT '0' COMMENT '配送員id',
`rider_name` varchar(100) NOT NULL DEFAULT '' COMMENT '配送員名字',
`order_state` tinyint(4) NOT NULL DEFAULT '0' COMMENT '配送狀態',
`is_deleted` tinyint(4) NOT NULL DEFAULT '0',
`grabbed_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '搶單時間',
`merchant_customer_distance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '商鋪到顧客步行距離',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_id` (`order_id`),
KEY `ix_created_at` (`created_at`),
KEY `ix_updated_at` (`updated_at`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='配送單';
複製代碼
看到末尾幾行的PRIMARY KEY
、UNIQUE KEY
和KEY
了嗎,它們就對應於DESC tb_order
結果中的PRI
、UNI
和MUL
,分別標識主鍵索引、惟一索引和普通索引。每一行括號內的字段就表示對應的索引列。
我嘗試了好幾種解釋清楚JOIN語法的方法(JOIN語法的確有些複雜),始終不能讓我本身滿意,最終決定仍是從一個例子開始。讓咱們首先看一張新的表,建表語句長下面這樣。
CREATE TABLE `tb_grab_order_limit` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
`rider_id` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '騎手id',
`order_grab_limit` INT(11) NOT NULL DEFAULT '0' COMMENT '接單上限',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '該記錄是否被刪除',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY(`id`),
KEY `ix_rider_id` (`rider_id`),
KEY `ix_created_at` (`created_at`),
KEY `ix_updated_at` (`updated_at`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8 comment="自定義騎手接單上限表";
複製代碼
小溫習
參考上面的建表語句嘗試回答下面這幾個問題。
order_grab_limit
這個字段的含義是什麼?沒錯!這就是自定義騎手接單上限表。描述了某一個騎手(rider_id
)對應的他的接單上限(order_grab_limit
)。表中的數據以下。
id | rider_id | order_grab_limit | is_deleted | created_at | updated_at |
---|---|---|---|---|---|
1 | 1 | 11 | 0 | 2018-02-25 17:22:03 | 2018-02-25 17:22:03 |
2 | 2 | 9 | 0 | 2018-02-25 17:22:21 | 2018-02-25 17:22:21 |
3 | 4 | 9 | 0 | 2018-02-25 17:22:31 | 2018-02-25 17:22:31 |
4 | 6 | 7 | 0 | 2018-02-25 17:22:39 | 2018-02-25 17:22:39 |
5 | 10 | 8 | 0 | 2018-02-25 17:22:46 | 2018-02-25 17:22:46 |
再讓咱們回顧一下前面反覆用到的tb_rider
表。
id | name | real_name_certify_state | level | level_city | is_deleted | created_at | updated_at |
---|---|---|---|---|---|---|---|
1 | Stark | 2 | 3 | 1 | 0 | 2017-01-01 22:00:19 | 2018-01-01 06:40:01 |
2 | Banner | 2 | 3 | 9 | 0 | 2017-04-28 12:01:19 | 2018-01-01 06:40:01 |
3 | Rogers | 2 | 2 | 1 | 0 | 2017-04-10 17:24:01 | 2018-01-01 06:40:01 |
4 | Thor | 1 | 0 | 1 | 0 | 2017-12-31 23:10:39 | 2018-01-01 06:40:01 |
5 | Natasha | 2 | 1 | 1 | 0 | 2017-02-11 15:03:13 | 2018-01-01 06:40:01 |
6 | Barton | 2 | 1 | 9 | 0 | 2017-02-11 15:04:19 | 2018-01-01 06:40:01 |
7 | Coulson | 2 | 3 | 9 | 0 | 2017-01-03 23:00:22 | 2018-01-01 06:40:01 |
8 | Coulson | 1 | 0 | 2 | 0 | 2017-01-05 10:10:23 | 2018-01-01 06:40:01 |
(終於鋪墊完啦!)
[1] 從LEFT JOIN開始
以這兩張表爲基礎,設想一個場景:假設要查詢tb_rider
表中全部騎手對應的自定義接單上限。咱們的Sql應該怎麼寫呢?
**思路1:**先查出tb_rider
表中全部騎手id,再根據這些騎手id做爲查詢條件,經過前面學習過的IN語法從tb_grab_order_limit
表中查詢出所對應的自定義接單上限的記錄。
SELECT id FROM tb_rider;
複製代碼
和
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (1, 2, 3, 4, 5, 6, 7, 8);
複製代碼
思路1顯然是個Bad idea。可是思路1詮釋瞭解決這個查詢問題的基本要點。
tb_rider
和tb_grab_order_limit
兩張表共同得出的;tb_rider
表中全部騎手,所以應該以tb_rider
表中的騎手id做爲查詢參考集合;tb_rider
表中的騎手都配置了自定義接單上限,思路1的查詢方案存在一個缺點,就是咱們須要根據查詢結果,在邏輯上作一個轉換得知哪些騎手沒有配置自定義接單上限( 不在返回結果中的騎手);**思路2:**基於這幾個要點咱們能夠使用LEFT JOIN語法,下面是對應的Sql語句。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
這裏先介紹一下JOIN語法的基本結構:表1 (INNER/LEFT/RIGHT/FULL) JOIN 表2 ON 表1.列1 = 表2.列2
。JOIN關鍵字先後鏈接的是兩張須要關聯查詢的數據表,ON關鍵字後面跟着關聯的條件。一共有四種類型的JOIN,他們分別是INNER JOIN、LEFT JOIN、RIGHT JOIN和FULL JOIN。以例子中的LEFT JOIN爲例,表1 LEFT JOIN 表2 ON 表1.列1 = 表2.列2
的含義是,遍歷表1中的列1的值,若是表2中列2的值有和它相等的則展現對應的記錄,若是沒有表2.列2和表1.列1相等,則展現爲null。
思路2的例子中,tb_rider LEFT JOIN tb_grab_order_limit ON tb_rider.id = tb_grab_order_limit.rider_id
的含義是,遍歷tb_rider
表中id
這一列(tb_rider
表的id
字段業務含義就是騎手id)的值,尋找tb_grab_order_limit
表中rider_id
列的值和它相等的記錄,若是不存在則是null。
咱們還看到SELECT語句的內容和咱們以前使用的很相似,但又稍微有點不同,都是表名.列名的書寫形式。其實這主要是指明瞭字段所屬的表,由於JOIN的兩張數據表中可能存在的相同名稱的列,例如tb_rider
表和tb_grab_order_limit
表都有id
字段,但含義大相徑庭,這樣寫更加明確。
最終思路2的結果以下。
id | order_grab_limit |
---|---|
1 | 11 |
2 | 9 |
4 | 9 |
6 | 7 |
7 | <null> |
8 | <null> |
5 | <null> |
3 | <null> |
咱們看到騎手id=(7, 8, 5, 3)的幾個騎手沒有配置自定義的接單上限,但由於是LEFT JOIN,他們仍然會展現在查詢結果中,不過由於沒有接單上限的記錄,order_grab_limit
的結果爲null。
讓咱們再回頭看一下表名.列名這個寫法。若是思路2中的Sql改爲下面這樣,返回結果會變成什麼呢?
SELECT tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
讓咱們來分析一下。咱們知道LEFT JOIN的返回結果集合是以它左側鏈接的數據表決定的,因此結果集仍然包含8條記錄,可是騎手id=(7, 8, 5, 3)這個騎手沒有對應的接單上限的配置,所以當咱們展現這幾個騎手的tb_grab_order_limit.rider_id
列的值的時候,相似於tb_grab_order_limit.order_grab_limit
,也是null。所以結果是下面這樣。
rider_id | order_grab_limit |
---|---|
1 | 11 |
2 | 9 |
4 | 9 |
6 | 7 |
<null> | <null> |
<null> | <null> |
<null> | <null> |
<null> | <null> |
若是你仍是不太明白,然咱們在SELECT的時候,加上tb_rider.id
,或許有助於理解。
SELECT tb_rider.id, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
結果是。
id | rider_id | order_grab_limit |
---|---|---|
1 | 1 | 11 |
2 | 2 | 9 |
4 | 4 | 9 |
6 | 6 | 7 |
7 | <null> | <null> |
8 | <null> | <null> |
5 | <null> | <null> |
3 | <null> | <null> |
[2] LEFT JOIN的姊妹篇:RIGHT JOIN
前面咱們知道LEFT JOIN是以鏈接的左側表做爲查詢的結果集的依據,RIGHT JOIN則是以鏈接的右側表做爲依據。讓咱們考慮另外一個場景:假設想要查詢全部設置了自定義接單上限的騎手姓名。應該如何寫這個Sql呢?
先在聰明的大腦裏思考幾分鐘。此時你須要類比LEFT JOIN,須要理解上一段內容講述的LEFT JOIN知識點,可能須要回到上一段再看一看示例Sql語句以及對應的結果。不要緊,一開始學習的時候慢慢來。
答案是這樣的。
SELECT tb_grab_order_limit.rider_id, tb_rider.name
FROM tb_rider RIGHT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
對應的查詢結果則是。
rider_id | name |
---|---|
1 | Stark |
2 | Banner |
4 | Thor |
6 | Barton |
10 | <null> |
若是這個結果和你腦海中思考的結果不同,不要着急,讓咱們再來解釋一下。RIGHT JOIN是以鏈接的右側表爲依據,而tb_grab_order_limit
中的騎手id=(1, 2, 4, 6, 10),其中騎手id爲10的騎手在tb_rider
表中是沒有的,因此name
爲null。
小測驗
嘗試下將上面的這條Sql語句改寫成LEFT JOIN吧(要求獲得相同的查詢結果)?
[3] 一絲不苟的INNER JOIN
之因此叫「一絲不苟」的INNER JOIN,是由於INNER JOIN是很是嚴格的關聯查詢,換句話說,必須是根據JOIN條件兩張表中存在匹配記錄的才做爲結果集返回。讓咱們回顧下[1]中LEFT JOIN的Sql。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
它的返回結果是。
id | order_grab_limit |
---|---|
1 | 11 |
2 | 9 |
4 | 9 |
6 | 7 |
7 | <null> |
8 | <null> |
5 | <null> |
3 | <null> |
若是咱們將LEFT JOIN改成INNER JOIN吶?修改後的Sql像這樣。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider INNER JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
這時返回的查詢結果變成了。
id | order_grab_limit |
---|---|
1 | 11 |
2 | 9 |
4 | 9 |
6 | 7 |
這是由於INNER JOIN會遍歷鏈接一側的表,根據ON後的鏈接條件,和鏈接另外一側的表進行比較,只有兩張表中存在匹配的記錄纔會做爲結果集返回。例如這裏,它會遍歷tb_rider
表中id
字段的值,而且去tb_grab_order_limit
表中尋找rider_id
與之匹配的記錄,若是找到則做爲結果返回。
B.T.W INNER JOIN 和 JOIN是等價的,換句話說,
表1 INNER JOIN 表2 ON...
和表1 JOIN 表2 ON...
是徹底等價的。
小測驗
猜想一下下面的這條Sql語句的返回結果是什麼?
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_grab_order_limit INNER JOIN tb_rider
ON tb_grab_order_limit.rider_id = tb_rider.id;
複製代碼
提示:這裏交換了一下INNER JOIN鏈接的兩張表的位置,根據INNER JOIN的特性,查詢結果會有影響嘛?
[4] 心大的FULL JOIN
FULL JOIN其實並不在意匹配與否,而是將鏈接的兩張表中全部的行都返回,若是有匹配的則返回匹配的結果,若是沒有匹配則哪張表中缺失則對應的將當前這條記錄標記爲null。看一個例子就明白啦!
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider FULL JOIN tb_grab_order_limit ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
這條Sql語句的查詢結果是這樣的。
id | name | rider_id | order_grab_limit |
---|---|---|---|
1 | Stark | 1 | 11 |
2 | Banner | 2 | 9 |
4 | Thor | 4 | 9 |
6 | Barton | 6 | 7 |
3 | Rogers | <null> | <null> |
5 | Natasha | <null> | <null> |
7 | Coulson | <null> | <null> |
8 | Coulson | <null> | <null> |
<null> | <null> | 10 | 10 |
能夠看到tb_rider
表中騎手id=(3, 5, 7, 8)的騎手在tb_grab_order_limit
表中沒有匹配的記錄,而tb_grab_order_limit
表中騎手id=(10)的騎手在tb_rider
表中沒有匹配記錄,可是它們都做爲結果集返回了。只不過缺失tb_grab_order_limit
記錄的,rider_id
和order_grab_limit
字段值爲null,而缺失tb_rider
記錄的,id
和name
字段的值爲null。
事實上,絕大多數狀況下,FULL JOIN都不會被用到。並且在一些數據庫管理系統中,例如MySql(咱們的線上環境主要使用的就是MySql),是不支持FULL JOIN語法的。對於上面的查詢語句,須要使用一些技巧經過LEFT JOIN、RIGHT JOIN以及UNION(這篇教程中咱們不討論UNION語法哦)語法的組合來實現一樣效果的查詢。
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit ON tb_rider.id = tb_grab_order_limit.rider_id
UNION
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.rider_id
FROM tb_rider RIGHT JOIN tb_grab_order_limit ON tb_rider.id = tb_grab_order_limit.rider_id
WHERE tb_rider.id IS null;
複製代碼
這已經超出了這篇教程的討論範圍啦!若是想要挑戰一下本身,如下是一些提示。
WHERE tb_rider.id IS null
是爲了對存在匹配的數據記錄去重(不然UNION以後會有重複的結果);試着在這兩條提示下理解一下這條Sql語句,若是可以弄明白這條語句是如何等價於FULL JOIN的,那麼說明你對JOIN家族的語法已經基本掌握啦。若是暫時還不能弄得很是明白也不要緊,多看一看例子,多寫一寫實踐一下,慢慢就會明白啦。
題外話
從上面的講解咱們瞭解到JOIN的四種用法,總結一下。
不過這些都是刻板的文字總結,讓咱們換個視角總結一下這集中JOIN語法。
離散數學中在討論集合論的時候介紹過**「韋恩圖」**的概念,它清楚的描述了數據集合之間的關係。而JOIN的這4種操做也正好對應了4種集合運算,下面的這張圖(Figure 1)很清楚的描述了這種關係。
再來看一下講述LEFT JOIN的開始,咱們提到的那個例子:查詢tb_rider
表中全部騎手對應的自定義接單上限。當時咱們首先提出了思路1,是分爲2個步驟的。
SELECT id FROM tb_rider;
複製代碼
和
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (1, 2, 3, 4, 5, 6, 7, 8);
複製代碼
咱們說這個思路很差,這是顯然的,由於在現實場景中每每數據集合都很大(例如這裏的rider_id
在現實中多是成百上千甚至成千上萬個),思路自己沒有問題但沒法操做執行。因此在4.3節咱們選擇經過JOIN語法來實現一樣的查詢。那是否是思路1就真的只能是個紙上談兵的思路了呢?固然不是啦!咱們還能夠使用嵌套的SELECT語句,就像這樣。
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (SELECT id FROM tb_rider);
複製代碼
這個寫法很是好理解,WHERE rider_id IN (SELECT id FROM tb_rider)
首先執行括號中的語句SELECT id FROM tb_rider
,而後執行IN篩選,就是咱們的思路1描述的那樣。因而獲得下面的結果。
rider_id | order_grab_limit |
---|---|
1 | 11 |
2 | 9 |
4 | 9 |
6 | 7 |
複習題
回想一下上面的結果和如下哪條Sql語句的執行結果是一致的呢?爲何是一致的,爲何和其餘的不一致?
1. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
2. SELECT tb_grab_order_limit.rider_id, tb_rider.name
FROM tb_rider RIGHT JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
3. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider INNER JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
4. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider FULL JOIN tb_grab_order_limit
ON tb_rider.id = tb_grab_order_limit.rider_id;
複製代碼
小測驗
思考一下如下這個場景,看看可否寫出它對應的Sql語句?
場景:篩選出全部經過實名認證(real_name_certify_state=2
)的金牌(level=3
)騎手(tb_rider
表),在2017-12-30當天(created_at >= xxx AND created_at < yyy
)所跑運單(tb_order
表)的運單號(order_id
)。
想想有幾種寫法呢?
前面的幾個段落咱們學習了Sql查詢中最經常使用,並且特別好用的語法知識,讓咱們簡單總結一下。
學習了這麼多知識點,實在是太膩害了!給本身點贊!
可是(凡事都有個可是)...
想要把這些知識點融會貫通,靈活應用到現實工做中更多變、更復雜的查詢場景,僅僅是「學會」是不夠的,還須要更多的「練習」和「回味」。
這個部分我設計了一個「闖關答題」項目,經過思考和回答這些闖關題,幫助你更好的掌握上面提到的知識點。
先來看一下答題將要用到的數據表。
[1] 商品數據表:tb_product
id | product_id | name | price |
---|---|---|---|
1 | 1001 | iPad Pro 10.5 64G WLAN | 4888 |
2 | 1002 | Macbook Pro 2017 13.3 i5/8G/256GB | 13888 |
3 | 1003 | iPhone X 64G | 8388 |
建表語句:
CREATE TABLE `tb_product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
`product_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品id',
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名稱',
`price` int(11) NOT NULL DEFAULT '0' COMMENT '商品價格',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_product_id` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='商品信息表';
複製代碼
字段含義:
id
:自增主鍵;product_id
:商品id;name
:商品名稱;price
:商品單價,單位是元;[2] 用戶數據表:tb_customer
id | customer_id | name | gender | balance |
---|---|---|---|---|
1 | NO100001 | 火火 | 女 | 18888 |
2 | NO100002 | 撥潑抹 | 女 | 9000 |
3 | NO100003 | 艾橋 | 男 | 7990 |
4 | NO100004 | 水娃 | 女 | 8388 |
建表語句:
CREATE TABLE `tb_customer` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
`customer_id` varchar(100) NOT NULL DEFAULT '' COMMENT '用戶id',
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '用戶姓名',
`gender` varchar(30) NOT NULL DEFAULT '' COMMENT '用戶性別',
`balance` int(11) NOT NULL DEFAULT '0' COMMENT '帳戶餘額',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_customer_id` (`customer_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='用戶信息表';
複製代碼
字段含義:
id
:自增主鍵;customer_id
:用戶id;name
:用戶姓名;gender
:用戶的性別;balance
:用戶當前的可用帳戶餘額,單位是元;[3] 訂單數據表:tb_order
id | order_id | customer_id | product_id | quantity |
---|---|---|---|---|
1 | NUM1000301 | NO100001 | 1001 | 1 |
2 | NUM1000302 | NO100001 | 1002 | 2 |
3 | NUM1000303 | NO100002 | 1002 | 2 |
4 | NUM1000304 | NO100003 | 1002 | 1 |
5 | NUM1000305 | NO100001 | 1003 | 1 |
建表語句:
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
`order_id` varchar(100) NOT NULL DEFAULT '' COMMENT '訂單id',
`customer_id` varchar(100) NOT NULL DEFAULT '0' COMMENT '用戶id',
`product_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品id',
`quantity` int(11) NOT NULL DEFAULT '0' COMMENT '商品價格',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_id` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='訂單數據表';
複製代碼
字段含義:
id
:自增主鍵;order_id
:訂單號;customer_id
:下單用戶id;product_id
:購買的商品id;quantity
:購買的數量;瞭解完須要用到表結構,咱們就要開始答題啦!
第一關:查詢帳戶餘額大於1萬元的用戶id和姓名?
Answer:
SELECT customer_id, name FROM tb_customer WHERE balance > 10000;
複製代碼
customer_id | name |
---|---|
NO100001 | 火火 |
第二關:查詢帳戶餘額小於1萬元且性別爲女生的用戶姓名?
Answer:
SELECT name FROM tb_customer WHERE balance < 10000 AND gender="女";
複製代碼
name |
---|
撥潑抹 |
水娃 |
第三關:查詢用戶id爲NO100001和NO100002的用戶,全部購買記錄的訂單號?
Hint:IN
Answer:
SELECT order_id FROM tb_order WHERE customer_id IN ("NO100001", "NO100002");
複製代碼
order_id |
---|
NUM1000301 |
NUM1000302 |
NUM1000303 |
NUM1000305 |
第四關:查詢用戶id爲NO10000一、NO100002兩位用戶全部的購買記錄(全部字段),要求按照優先以商品id遞增、其次以訂單號遞減的規則展現數據?
Hint:IN、ORDER BY
Answer:
SELECT * FROM tb_order WHERE customer_id IN ("NO100001", "NO100002")
ORDER BY product_id ASC, order_id DESC;
複製代碼
id | order_id | customer_id | product_id | quantity |
---|---|---|---|---|
1 | NUM1000301 | NO100001 | 1001 | 1 |
3 | NUM1000303 | NO100002 | 1002 | 2 |
2 | NUM1000302 | NO100001 | 1002 | 2 |
5 | NUM1000305 | NO100001 | 1003 | 1 |
第五關:查詢性別爲女生的用戶總數?
Hint:COUNT
Answer:
SELECT COUNT(customer_id) FROM tb_customer WHERE gender="女";
複製代碼
COUNT(customer_id) |
---|
3 |
第六關:查詢NO10000一、NO10000二、NO100003三位用戶各自購買商品的總數(不區分商品類型),輸出購買商品件數大於等於2件的用戶id以及他們對應購買的商品總數?
Warning:「購買商品的總數」和上一關「女生用戶的總數」,這兩個**「總數」**同樣嗎?
Hint:IN、SUM、HAVING
Answer:
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN ("NO100001", "NO100002", "NO100003")
GROUP BY customer_id
HAVING SUM(quantity) >= 2;
複製代碼
customer_id | SUM(quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
第七關:查詢NO10000一、NO10000二、NO100003三位用戶各自購買商品的總數(不區分商品類型),輸出購買總數前兩名的用戶id以及他們對應購買的商品總數?
Hint:IN、SUM、ORDER BY、LIMIT
Answer:
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN ("NO100001", "NO100002", "NO100003")
GROUP BY customer_id
ORDER BY SUM(quantity) DESC
LIMIT 2;
複製代碼
customer_id | SUM(quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
第八關:查詢全部用戶各自購買商品的總數(不區分商品類型),輸出購買商品件數大於等於2件的用戶id以及他們對應購買的商品總數?要求給出至少兩種寫法。
Warning:注意是「全部用戶」,不是全部的用戶都購買了商品
Hint:關聯查詢有哪些方法?
Answer:
寫法一:嵌套的SELECT
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (SELECT customer_id FROM tb_customer)
GROUP BY customer_id
HAVING SUM(quantity) >= 2;
複製代碼
customer_id | SUM(quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
寫法二:使用LEFT JOIN語法
SELECT tb_customer.customer_id, SUM(tb_order.quantity) FROM tb_customer
LEFT JOIN tb_order ON tb_customer.customer_id = tb_order.customer_id
GROUP BY tb_customer.customer_id
HAVING SUM(tb_order.quantity) >= 2;
複製代碼
customer_id | SUM(tb_order.quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
第九關:查詢全部用戶各自購買商品的總數(不區分商品類型),輸出購買總數前兩名的用戶id以及他們對應購買的商品總數?要求給出至少兩種寫法。
Hint:關聯查詢有哪些方法?
Answer:
寫法一:嵌套的SELECT
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (SELECT customer_id FROM tb_customer)
GROUP BY customer_id
ORDER BY SUM(quantity) DESC
LIMIT 2;
複製代碼
customer_id | SUM(quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
寫法二:使用LEFT JOIN語法
SELECT tb_customer.customer_id, SUM(tb_order.quantity) FROM tb_customer
LEFT JOIN tb_order ON tb_customer.customer_id = tb_order.customer_id
GROUP BY tb_customer.customer_id
ORDER BY SUM(tb_order.quantity) DESC
LIMIT 2;
複製代碼
customer_id | SUM(tb_order.quantity) |
---|---|
NO100001 | 4 |
NO100002 | 2 |
第十關:如下哪幾條Sql語句使用到了索引?分別是哪些字段上的索引?是什麼類型的索引?
1. SELECT name FROM tb_customer WHERE customer_id = 1001;
2. SELECT product_id, name FROM tb_product WHERE price > 5000;
3. SELECT order_id, customer_id, product_id FROM tb_order
WHERE order_id = "NUM1000302" AND customer_id = "NO100001"
AND product_id = "1002";
4. SELECT order_id FROM tb_order WHERE id > 2;
複製代碼
Hint:索引
Answer:
sql序號 | 是否使用到索引 | 索引所在字段 | 索引類型 |
---|---|---|---|
1 | 是 | customer_id | UNIQUE KEY |
2 | 否 | - | - |
3 | 是 | order_id | UNIQUE KEY |
4 | 是 | id | PRIMARY KEY |
閱讀博客還不過癮?
歡迎你們掃二維碼經過添加羣助手,加入交流羣,討論和博客有關的技術問題,還能夠和博主有更多互動
博客轉載、線下活動及合做等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通