寫給運營同窗和初學者的SQL入門教程

做者簡介html

多肉,餓了麼資深python工程師。曾在17年擔任餓了麼即時配送衆包系統的研發經理,這篇文章最先的版本就誕生於那段時間,目前負責配送相關業務系統的總體穩定性建設。我的比較喜歡c和python,最近有點迷rust,同時仍是個archlinux的平常用戶,有相似愛好的歡迎交流python

Preface

爲何以《寫給運營同窗和初學者的Sql入門教程》爲題?linux

這本來是給一位關係要好的運營同窗定製的Sql教程。在餓了麼,總部運營的同窗在排查、跟蹤線上問題和作運營決策的時候,除了經過運營管理系統查詢信息和依賴數據分析師給出的分析數據,經常也須要直接從數據庫管理臺經過寫Sql的方式獲取更細緻、更實時的業務數據,並基於這些數據進行一些及時的分析,從而更快的給出運營方案。在這樣的背景下,Sql已經愈來愈成爲咱們運營同窗的一項必備技能。網上有不少Sql教程(e.g. w3school),我也翻閱過一些運營同窗桌上的Sql紙質書,這些教程都很好,但廣泛側重介紹語法,不少不少的語法,配以簡短的demo。做爲老司機的reference book很贊,但是對於剛入門甚至尚未入門的學習者,就未免困難了一點。再回顧運營同窗的使用場景,大多數狀況下是根據一些已有條件作一些簡單的查詢,偶爾會有一些相對複雜的查詢,好比對查詢結果作一些聚合、分組、排序,或是同時查詢兩三張數據表,除此之外,建表、建索引、修改表字段、修改字段值等等這些操做,在運營同窗的平常工做中基本是不會遇到的。git

基於以上種種緣由,寫了這篇教程,初衷是可以幫助這位好朋友以更高的ROI入門Sql。下面是寫這篇教程時的一些考量:github

  1. 從數據庫、數據表的基礎概念以及最簡單的Sql語法開始,作一些必要的鋪墊,後續的每一章再逐步增長難度;
  2. 只介紹查詢語法(更準確的說,是最經常使用的查詢語法),將它們的用法和套路解釋清楚,不涉及平常工做暫時還用不到的建表、修改表結構等等操做,避免鋪天蓋地的語法一個接一個反而干擾了學習的節奏;
  3. 經過穿插一些小測驗、小溫習,及時檢驗對知識點的理解和複習已經學習過的知識點;
  4. 結合一些業務場景的demo幫助理解;
  5. 結尾提供一章快速複習,既是複習,也能夠自測還有哪些知識點沒有掌握到位;

建議全部閱讀教程的同窗,都嘗試搭建一套本身的數據庫服務(建議安裝MySQL),對教程中的demo多作一些練習,不管是示例、小測驗仍是小溫習裏面的Sql語句,都不妨親自執行一下,這也是一種很好的幫助你熟悉語法的方式。固然搭建本身的數據庫會是一個不小的挑戰,寫做這篇教程的時候,我在本身的VPS上安裝了MySQL(MariaDB)並提供了一個鏈接腳本(隱藏了鏈接MySQL的過程)給朋友使用,可是這種方式並不適合推廣到全部人。具體的安裝和使用方式,不在本教程的敘述範圍內,因此...運營妹子們能夠求助下熟悉的研發同窗,漢子們嘛..算法

  1. 數據建立腳本-經過該腳本導入demo數據到MySQL中
  2. 連接VPS上MySQL的腳本-基本原理是創建了一個ssh tunnel,在tunnel裏和遠端的MySQL通訊,但實際上本地建MySQL服務更方便,僅供參考

能夠從這裏sql_tutorial下載經過pandoc+latex導出的pdf,得到更好的閱讀體驗。sql

B.T.W數據庫

由餓了麼技術社區主辦的首屆物流技術開放日終於來啦!編程

時間:2018年12月30日後端

地點:餓了麼上海總部:普陀區近鐵城市廣場北座5樓榴蓮酥

這次活動邀請到了物流團隊的6位重量級嘉賓。不只會有先後端大佬分享最新的架構、算法在物流團隊的落地實戰經驗,更有 P10 大佬教你如何在業務開發中得到技術成長。固然,也會有各類技術書籍,記念品拿到手軟,最後最重要的一點,徹底免費!還等什麼,趕快點擊 etech.ele.me/salon.html?… 瞭解更多細節並報名吧!

👆我也會在此次開放日活動中分享Gunicorn有關的話題,歡迎你們報名參加。放出一張分享內容的Outline:

by 多肉

Introduction

其實Sql並無那麼難。Sql是幫助你和關係型數據庫交互的一套語法,主要支持的操做有4類:

  1. DQL:其實就是數據查詢操做,經過特定的語法查詢到數據庫裏你想要的數據結果,而且展現出來;
  2. DML:數據修改操做,包括更新某個數據字段、向某張表裏面插入一條新的數據等等;
  3. DDL:數據定義操做,好比建立一張新的表或是建立一個新的索引(索引是啥?咱們後面專門聊一聊它);
  4. DCL:數據受權操做,好比受權特定的人能夠查詢某張特定的表;

聽起來挺嚇人的對吧,但實際上DML、DDL、DCL這3類操做在平常的運營工做中幾乎都不會用到,常常會使用到的吶實際上是第一種,也就是數據查詢操做(DQL)。Sql基本的查詢語法也比較簡單,那麼難在哪裏呢?我猜測難在學習了基本語法以後,不知道怎麼應用到實際的Case上。在接下來的內容裏,我將以一些十分接近現實的衆包運營Case爲例,逐一解釋最基本的Sql查詢語法而且分析如何將它應用到具體的場景上。

1 預備知識

好的吧,吹了一波牛仍是逃不過須要介紹一些最基礎的東西,可是我保證這是整篇教程中最枯燥的部分,後面就會有趣不少。

1.1 數據庫和數據表

爲了更簡單的理解這兩個概念以及他們之間的關係,能夠這麼類比:

  1. 數據表:就是一張表格,想象一張你常常在使用的Excel表,有行有列,每一行就是一條數據,每一列對應了這條數據的某一個具體的字段,固然這張表還有一個表名。數據表也是如此,只是字段名、表名不像Excel表格那樣好理解,好比Excel表格裏面某一列的字段名可能叫騎手id,而對應到數據表裏面可能就叫作rider_id,Excel的表名可能叫騎手基本信息表,而對應到數據表的表名則可能叫tb_rider
  2. 數據庫:就是集中管理一批相關的表格的地方。你能夠把它理解成是一個文件夾。平時你可能會建立一個名叫衆包業務的文件夾,而後將衆包運營相關的Excel表格都放在這個文件夾裏面。數據庫也是如此,好比咱們會有一個庫名叫作crowd的數據庫,裏面可能存放了tb_ridertb_order等和騎手、運單相關的多張數據表;

因此,「關係型數據庫」的概念很嚇唬人,但其實道理很簡單,就是列和列之間有必定的聯繫,整合在一塊兒就是一條有意義的數據,將這些數據概括起來就構成了一張表,而將一批有關聯的表一同管理起來就獲得了一個數據庫。

1.2 最基本的Sql查詢語法

最基本的Sql查詢語法其實就一個:

SELECT 列名(或者*,表示全部列) FROM 表名 WHERE 篩選條件;
複製代碼

B.T.W 注意SELECT...FROM...WHERE...;語句結尾的這個分號,在標準Sql語法中這個分號是必要的

讓咱們按照FROMWHERESELECT的順序理解一下這個語法:

  1. FROM 表名:顧名思義,就是從表名指定的這張表格中;
  2. WHERE 篩選條件:意思是「當知足篩選條件」的時候;
  3. 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";
複製代碼

2 更進一步

剛剛咱們學習了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

字段含義:

  1. id:自增主鍵。又是一個聽起來很嚇人的名字,但實際含義很簡單。「自增」的意思是,每次在這張數據表中建立一條新記錄的時候,數據庫都會在上一個id值的基礎上自動加上一個固定的步長(默認就是+1)做爲新記錄的id值。而所謂「主鍵」,就是可以在一張數據表中惟一標識一條記錄的字段,由於每條記錄的id都不同,因此它是主鍵。這個字段能夠是爲了單純的標識數據的惟一性,也能夠具備一些業務含義,好比這裏的id就同時也是騎手的帳號id;
  2. name: 騎手姓名;
  3. real_name_certify_state: 實名認證狀態:1-認證中,2-認證經過,3-認證失敗;
  4. level:騎手等級,3-金牌,2-銀牌,1-銅牌,0-普通;
  5. level_city:等級城市;
  6. is_deleted:這條數據是否有效,在大多數產線相關的數據表中,都會有這樣的標記字段。0-未刪除(表示有效), 1-已刪除(表示無效);
  7. created_at:這條數據的建立時間,也是全部產線數據表中都必須有的字段;
  8. 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

字段含義:

  1. id:自增主鍵。吶,這裏的id就是單純的主鍵做用,沒有其餘的業務含義;
  2. order_id:運單號,業務層面上運單的惟一標識;
  3. order_state:運單當前的狀態。10-待搶單,20-待到店,80-待取餐,40-已送達;
  4. rider_id:搶單的騎手id,還未被搶的運單這個字段是默認值0;
  5. rider_name:搶單的騎手姓名,還未被搶的運單這個字段是默認值空字符;
  6. grabbed_time:搶單時間,還未被搶的運單這個字段是默認的"1970-01-01 00:00:00"(這是一個特殊的時間,有興趣的話能夠搜索關鍵詞:時間戳);
  7. created_at:這條數據的建立時間,也是全部產線數據表中都必須有的字段;
  8. 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;
複製代碼

2.1 IN 操做

場景: 線下反饋了一批騎手說本身理應是上海的金牌,可是牌級是普通或者展現的是金牌卻沒有享受到上海的金牌活動,你已經知道了這幾個分別是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這種被設計爲惟一值的字段做爲查詢依據時,返回的結果也是惟一的,而當你使用相似騎手姓名這類字段做爲查詢依據時,就有可能出現上面這種狀況。這時候你就須要依賴更多的信息來判斷,哪一條纔是你真正想要的。因此可以用明確的字段做爲查詢依據時就要儘量的使用。

2.2 關係運算符:AND 和 OR

最經常使用的關係運算符有兩個ANDOR,用來鏈接多個篩選條件。顧名思義,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當天,因此前者是<=,後者是>=

固然啦,ANDOR這樣的關係運算符,不只僅可以鏈接先後兩個篩選條件,也能夠經過使用若干個ANDOR鏈接多個不一樣的篩選條件。好比:想要看看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;
複製代碼

2.3 排序:ORDER BY

讓咱們先小小的複習一下上面學到的知識點,有一個這樣的場景:

咱們打算看一下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;
複製代碼

若是你沒有寫對,不要緊,讓咱們來分析一下:

  1. Stark這位騎手的騎手id是1,因此咱們的第一個篩選條件爲rider_id = 1
  2. 由於咱們要看2017-12-30當天搶單的運單id,肯定了咱們的第二個篩選條件是搶單時間,對應的是grabbed_time這個字段,而2017-12-30當天,實際上指的就是2017-12-30 00:00:00(包括)到2017-12-31 00:00:00(不包括)這段時間;
  3. 最後是已完成這個條件,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 = 2order_state = 80的數據,也依然不可能排在rider_id = 1order_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

這個部分相對有一點難,能夠多對比着例子理解一下。

3 高級一點的話題

進入到這個部分,說明以前的內容你基本都已經掌握了,在平常運營的操做中有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)。

3.1 聚合函數:COUNT,SUM, AVG

千萬別被聚合函數這個名字嚇唬到,能夠簡單的理解爲對數據進行一些加工處理,讓咱們先來分別看一下這幾個聚合函數的基本定義。

  1. COUNT:對查詢結果集合中特定的列進行計數;
  2. SUM:對查詢結果的某個字段進行求和;
  3. 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的兩個小脾氣。

  1. COUNT不會自動去重;
  2. 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語法,以及如何將它們和聚合函數結合使用,會更有難度一些。

3.2 對查詢結果去重:DISTINCT 語法

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這個聚合函數一塊兒使用,這樣就能夠對去重後的結果進行計數。

3.3 將查詢數據分組:GROUP BY 語法

前面咱們基於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語句

  1. 查詢2017-12-30當天建立的運單,狀態爲已完成且配送距離大於等於2千米的總單量;
  2. 查詢2017-12-30當天建立且狀態爲已完成的全部運單的平均配送距離;
  3. 查詢2017-12-30當天完成過配送任務(至少配送完成1單)的騎手總人數;

複習題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當天每一個參與跑單的騎手各自的完成單總量;
  2. 查詢2017-12-30當天每一個參與跑單騎手的完成單平均配送距離;

首先分析一下這裏的場景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列的求平均值。

3.4 聚合函數的好搭檔:HAVING 語法

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」的篩選條件,因此沒有在咱們的查詢結果中。

4 有點超綱的話題

4.1 字段類型

類型這個詞此刻你聽起來可能仍是很陌生的,但其實在計算機科學領域,類型是一個很是基礎並且普遍存在的概念,幾乎每一種編程語言都有本身的類型系統。

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,這段二進制流能夠表示不少東西,要明確它的含義,就須要明確它的類型,好比下面這兩種不一樣的類型,這段流表示的內容就徹底不一樣。

  1. 若是咱們把這段二進制流看做是32位整型,那麼它表明的是10322903這個整數;
  2. 若是咱們把這段二進制流看做是2個16位整型(前16位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

讓咱們分析一下獲取這個結果的整個流程,幫助你理解一下,類型是如何發揮做用的。

  1. 這條Sql語句會被執行,根據主鍵id找到對應的行,進而獲取到這一行、score這列的值;
  2. 可是咱們上面說到,計算機的存儲是基於二進制的,因此獲取到的score值是相似於00000000000000000000000001100100這樣的二進制流;
  3. 這時候根據tb_stu_math_score表的定義,score這一列被定義爲整型,因而將二進制流轉化爲一個整型數,通過進制轉換獲得00000000000000000000000001100100對應的整型值爲100,因而咱們看到的結果就是100。

實際上反過來也很是相似,當咱們向這張表中寫入數據時,例如寫入的score列的值爲100。由於存儲基於二進制,根據表的定義,score列的類型爲整型,因而將值100按照整型轉換爲對應的二進制流00000000000000000000000001100100,而且寫入到庫中。

[2] Sql的主要數據類型有哪些?

Sql中經常接觸的數據類型主要包括幾類。

1 整型

  1. tinyint:用來表示很小很小的整數,好比經常用它做爲is_deletedis_valid這些字段的字段類型,由於這兩個字段表示該條記錄是否有效,只存在兩個值分別是0和1;
  2. smallint:比tinyint稍微大一點點的整型,能夠表示更大一點的整數,好比200、40四、401這樣的整數值;
  3. int:經常使用的整型,能夠用來表示比較大的整數,好比10322(事實上int能夠表示的整數範圍遠遠比這個大);
  4. bigint:用來表示很是大的整數,好比大多數表的自增id就會使用這個類型,能夠表示相似10322903這樣很是大的整數(事實上bigint能夠表示的整數範圍遠遠比這個要大);

2 浮點型

  1. decimal:能夠表示很是準確的小數,好比經緯度;

3 字符串類型

  1. char:固定長度的字符串;
  2. varchar:可變長度的字符串;

這裏固定長度和可變長度指的是數據庫中的存儲形式,由於這部分的內容其實有些超出了這個教程的範圍,咱們不過多的解釋這裏的區別。通常在咱們實際的應用中varchar用的更多一些。它們都表示相似於"very glad to meet u, Huohuo!"這樣的一串字符,固然也能夠是中文"敲開心認識你,火火!"

4 日期類型

  1. date:表示一個日期,只包含日期部分,不包含時間,好比當前日期"2018-01-23";
  2. 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 '姓名'
複製代碼

來解釋一下這裏的語句。

  1. name是字段名(列名);
  2. varchar表示字段類型爲字符串;
  3. NOT NULL表示這個字段不能爲空, 爲空的意思是沒有指定任何值給這個字段(注意不等價於空字符串);
  4. ```DEFAULT ''`表示若是沒有指定這個字段的值則使用空字符串做爲默認值;
  5. COMMENT '姓名'是對這個字段的備註,表示這個字段的業務含義,只用作展現;

4.2 索引

索引絕對算得上是關係型數據庫中最關鍵同時也是最有難度的話題。即使是經驗豐富的研發同窗,也常常會踩到索引的坑。不過咱們這裏介紹索引,只是爲了更好的服務於查詢,我會盡量避免牽扯進一些複雜的概念和底層原理。

[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_idrider_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是空的,這就表示這一列(或者叫這個字段)不是索引列(或者叫索引字段)。但idorder_idcreated_atupdated_at這幾列對應的Key均是有值的,這說明這幾列都是索引列。但這幾列Key的值又各不相同,這是爲啥吶?這是之內索引也分爲不一樣的類型,讓咱們逐個來解釋一下。

  1. PRI:是primary的縮寫,標記這一列爲主鍵,主鍵的概念咱們在一開始的時候有介紹過,就是用來惟一標識表中每一行數據的索引;
  2. UNI: 是unique的縮寫,顧名思義就是惟一的意思, 被設置爲UNI KEY的列,不容許出現重複的值,若是嘗試向表中插入這一列的值徹底相同的兩行數據,則會引起報錯。我猜你確定會以爲疑惑,那UNI KEYPRI KEY有啥區別?首先是這兩種類型的索引在實現上是有區別的(這一點我們不深究,涉及到了數據庫底層對索引的實現),其次PRI KEY更多的是數據庫層面的語義,僅僅是描述數據的惟一性,而UNI KEY則更可能是業務層面的語義,好比說這裏的order_id字段,由於業務上不能存在兩個運單號徹底相同的運單,因此須要把order_id這一列設置爲UNI KEY
  3. 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 KEYUNIQUE KEYKEY了嗎,它們就對應於DESC tb_order結果中的PRIUNIMUL,分別標識主鍵索引、惟一索引和普通索引。每一行括號內的字段就表示對應的索引列。

4.3 JOIN語法家族

我嘗試了好幾種解釋清楚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="自定義騎手接單上限表";
複製代碼

小溫習

參考上面的建表語句嘗試回答下面這幾個問題。

  1. 這張表的表名是什麼?
  2. order_grab_limit這個字段的含義是什麼?
  3. 這張表的主鍵索引是什麼?有幾個惟一索引、幾個普通索引?

沒錯!這就是自定義騎手接單上限表。描述了某一個騎手(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詮釋瞭解決這個查詢問題的基本要點。

  1. 咱們最終想要的數據是須要結合tb_ridertb_grab_order_limit兩張表共同得出的;
  2. 關聯這兩張數據表的條件是騎手id;
  3. 由於查詢的要求是:tb_rider表中全部騎手,所以應該以tb_rider表中的騎手id做爲查詢參考集合;
  4. 不是全部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_idorder_grab_limit字段值爲null,而缺失tb_rider記錄的,idname字段的值爲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;
複製代碼

這已經超出了這篇教程的討論範圍啦!若是想要挑戰一下本身,如下是一些提示。

  1. UNION鏈接兩條SELECT語句,做用是將兩個SELECT語句的查詢結果取交集;
  2. 第2條SELECT語句中的WHERE tb_rider.id IS null 是爲了對存在匹配的數據記錄去重(不然UNION以後會有重複的結果);
  3. WHERE語句是在RIGHT JOIN以後,UNION以前執行的;

試着在這兩條提示下理解一下這條Sql語句,若是可以弄明白這條語句是如何等價於FULL JOIN的,那麼說明你對JOIN家族的語法已經基本掌握啦。若是暫時還不能弄得很是明白也不要緊,多看一看例子,多寫一寫實踐一下,慢慢就會明白啦。

題外話

從上面的講解咱們瞭解到JOIN的四種用法,總結一下。

  1. INNER JOIN關鍵字在兩張表中都有匹配的值的時候返回匹配的行;
  2. LEFT JOIN關鍵字從左表返回全部的行,即便在右表中沒有匹配的行;
  3. RIGHT JOIN關鍵字從右表返回全部的行,即便在左表中沒有匹配的行;
  4. FULL JOIN關鍵字從左表和右表那裏返回全部行,即便右表的行在左表中沒有匹配或者左表的行在右表中沒有匹配,這些行也會返回;

不過這些都是刻板的文字總結,讓咱們換個視角總結一下這集中JOIN語法。

離散數學中在討論集合論的時候介紹過**「韋恩圖」**的概念,它清楚的描述了數據集合之間的關係。而JOIN的這4種操做也正好對應了4種集合運算,下面的這張圖(Figure 1)很清楚的描述了這種關係。

Mathematical Principle of Sql Join Syntax

4.4 嵌套的SELECT語法

再來看一下講述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)。

想想有幾種寫法呢?

5 闖關答題:快速複習

前面的幾個段落咱們學習了Sql查詢中最經常使用,並且特別好用的語法知識,讓咱們簡單總結一下。

  1. 數據庫、數據表的概念;
  2. 最基本的Sql查詢結構;
  3. IN查詢和邏輯操做語法(AND/OR);
  4. 對查詢結果進行排序和LIMIT語法;
  5. 聚合函數(COUNT/AVG/SUM)和DISTINCT語法;
  6. 對查詢結果分組(GROUP BY);
  7. 對聚合函數的結果進行篩選的HAVING語法;
  8. 字段類型和索引的概念和做用;
  9. JOIN語法的一家子(LEFT JOIN/RIGHT JOIN/INNER JOIN/FULL JOIN);
  10. 嵌套的SELECT語法;

學習了這麼多知識點,實在是太膩害了!給本身點贊!

可是(凡事都有個可是)...

想要把這些知識點融會貫通,靈活應用到現實工做中更多變、更復雜的查詢場景,僅僅是「學會」是不夠的,還須要更多的「練習」和「回味」。

這個部分我設計了一個「闖關答題」項目,經過思考和回答這些闖關題,幫助你更好的掌握上面提到的知識點。

先來看一下答題將要用到的數據表。

[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='商品信息表';
複製代碼

字段含義:

  1. id:自增主鍵;
  2. product_id:商品id;
  3. name:商品名稱;
  4. 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='用戶信息表';
複製代碼

字段含義:

  1. id:自增主鍵;
  2. customer_id:用戶id;
  3. name:用戶姓名;
  4. gender:用戶的性別;
  5. 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='訂單數據表';
複製代碼

字段含義:

  1. id:自增主鍵;
  2. order_id:訂單號;
  3. customer_id:下單用戶id;
  4. product_id:購買的商品id;
  5. 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 進行溝通
相關文章
相關標籤/搜索