SQL 不一樣於與其餘編程語言的最明顯特徵是處理代碼的順序。在大數編程語言中,代碼按編碼順序被處理,可是在SQL語言中,第一個被處理的子句是FROM子句,儘管SELECT語句第一個出現,可是幾乎老是最後被處理。mysql
每一個步驟都會產生一個虛擬表,該虛擬表被用做下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只是最後一步生成的表纔會返回 給調用者。若是沒有在查詢中指定某一子句,將跳過相應的步驟。sql
先來一段僞代碼,首先你能看懂麼?數據庫
SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> ORDER BY <order_by_condition> LIMIT <limit_number>
若是你知道每一個關鍵字的意思,做用,若是你還用過的話,那再好不過了。可是,你知道這些語句,它們的執行順序你清楚麼?編程
首先聲明下,一切測試操做都是在MySQL數據庫上完成,關於MySQL數據庫的一些簡單操做,請閱讀一下文章:緩存
繼續作如下的前期準備工做:編程語言
一、新建一個測試數據庫TestDB;函數
create database TestDB;
二、建立測試表table1和table2;測試
CREATE TABLE table1 ( customer_id VARCHAR(10) NOT NULL, city VARCHAR(10) NOT NULL, PRIMARY KEY(customer_id) )ENGINE=INNODB DEFAULT CHARSET=UTF8; CREATE TABLE table2 ( order_id INT NOT NULL auto_increment, customer_id VARCHAR(10), PRIMARY KEY(order_id) )ENGINE=INNODB DEFAULT CHARSET=UTF8;
三、插入測試數據;大數據
INSERT INTO table1(customer_id,city) VALUES('163','hangzhou'); INSERT INTO table1(customer_id,city) VALUES('9you','shanghai'); INSERT INTO table1(customer_id,city) VALUES('tx','hangzhou'); INSERT INTO table1(customer_id,city) VALUES('baidu','hangzhou'); INSERT INTO table2(customer_id) VALUES('163'); INSERT INTO table2(customer_id) VALUES('163'); INSERT INTO table2(customer_id) VALUES('9you'); INSERT INTO table2(customer_id) VALUES('9you'); INSERT INTO table2(customer_id) VALUES('9you'); INSERT INTO table2(customer_id) VALUES('tx'); INSERT INTO table2(customer_id) VALUES(NULL);
準備工做作完之後,table1和table2看起來應該像下面這樣:編碼
mysql> select * from table1; +-------------+----------+ | customer_id | city | +-------------+----------+ | 163 | hangzhou | | 9you | shanghai | | baidu | hangzhou | | tx | hangzhou | +-------------+----------+ 4 rows in set (0.00 sec) mysql> select * from table2; +----------+-------------+ | order_id | customer_id | +----------+-------------+ | 1 | 163 | | 2 | 163 | | 3 | 9you | | 4 | 9you | | 5 | 9you | | 6 | tx | | 7 | NULL | +----------+-------------+ 7 rows in set (0.00 sec)
四、準備SQL邏輯查詢測試語句
SELECT a.customer_id, COUNT(b.order_id) as total_orders FROM table1 AS a LEFT JOIN table2 AS b ON a.customer_id = b.customer_id WHERE a.city = 'hangzhou' GROUP BY a.customer_id HAVING count(b.order_id) < 2 ORDER BY total_orders DESC;
使用上述SQL查詢語句來得到來自杭州,而且訂單數少於2的客戶。
還記得上面給出的那一長串的SQL邏輯查詢規則麼?那麼,到底哪一個先執行,哪一個後執行呢?如今,我先給出一個查詢語句的執行順序:
(7) SELECT (8) DISTINCT <select_list> (1) FROM <left_table> (3) <join_type> JOIN <right_table> (2) ON <join_condition> (4) WHERE <where_condition> (5) GROUP BY <group_by_list> (6) HAVING <having_condition> (9) ORDER BY <order_by_condition> (10) LIMIT <limit_number>
上面在每條語句的前面都標明瞭執行順序號,那麼各條查詢語句是如何執行的呢?
邏輯查詢處理階段簡介
注:
笛卡爾積簡單介紹:假設集合A={a, b},集合B={0, 1, 2},則兩個集合的笛卡爾積爲{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。
步驟10,按ORDER BY子句中的列列表排序上步返回的行,返回遊標VC10.這一步是第一步也是惟一一步可使用SELECT列表中的列別名的步驟。這一步不一樣於其它步驟的 是,它不返回有效的表,而是返回一個遊標。SQL是基於集合理論的。集合不會預先對它的行排序,它只是成員的邏輯集合,成員的順序可有可無。對錶進行排序 的查詢能夠返回一個對象,包含按特定物理順序組織的行。ANSI把這種對象稱爲遊標。理解這一步是正確理解SQL的基礎。
由於這一步不返回表(而是返回遊標),使用了ORDER BY子句的查詢不能用做表表達式。表表達式包括:視圖、內聯表值函數、子查詢、派生表和共用表達式。它的結果必須返回給指望獲得物理記錄的客戶端應用程序。例如,下面的派生表查詢無效,併產生一個錯誤:
select * from(select orderid,customerid from orders order by orderid) as d
下面的視圖也會產生錯誤:
create view my_view as select * from orders order by orderid
在SQL中,表表達式中不容許使用帶有ORDER BY子句的查詢,而在T—SQL中卻有一個例外(應用TOP選項)。
因此要記住,不要爲表中的行假設任何特定的順序。換句話說,除非你肯定要有序行,不然不要指定ORDER BY 子句。排序是須要成本的。
在這些SQL語句的執行過程當中,都會產生一個虛擬表,用來保存SQL語句的執行結果(這是重點),我如今就來跟蹤這個虛擬表的變化,獲得最終的查詢結果的過程,來分析整個SQL邏輯查詢的執行順序和過程。
第一步,執行FROM
語句。咱們首先須要知道最開始從哪一個表開始的,這就是FROM
告訴咱們的。如今有了<left_table>
和<right_table>
兩個表,咱們到底從哪一個表開始,仍是從兩個表進行某種聯繫之後再開始呢?它們之間如何產生聯繫呢?——笛卡爾積
關於什麼是笛卡爾積,請自行Google補腦。通過FROM語句對兩個表執行笛卡爾積,會獲得一個虛擬表,暫且叫VT1(vitual table 1),內容以下:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 9you | shanghai | 1 | 163 | | baidu | hangzhou | 1 | 163 | | tx | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 2 | 163 | | baidu | hangzhou | 2 | 163 | | tx | hangzhou | 2 | 163 | | 163 | hangzhou | 3 | 9you | | 9you | shanghai | 3 | 9you | | baidu | hangzhou | 3 | 9you | | tx | hangzhou | 3 | 9you | | 163 | hangzhou | 4 | 9you | | 9you | shanghai | 4 | 9you | | baidu | hangzhou | 4 | 9you | | tx | hangzhou | 4 | 9you | | 163 | hangzhou | 5 | 9you | | 9you | shanghai | 5 | 9you | | baidu | hangzhou | 5 | 9you | | tx | hangzhou | 5 | 9you | | 163 | hangzhou | 6 | tx | | 9you | shanghai | 6 | tx | | baidu | hangzhou | 6 | tx | | tx | hangzhou | 6 | tx | | 163 | hangzhou | 7 | NULL | | 9you | shanghai | 7 | NULL | | baidu | hangzhou | 7 | NULL | | tx | hangzhou | 7 | NULL | +-------------+----------+----------+-------------+
總共有28(table1的記錄條數 * table2的記錄條數)條記錄。這就是VT1的結果,接下來的操做就在VT1的基礎上進行。
執行完笛卡爾積之後,接着就進行ON a.customer_id = b.customer_id
條件過濾,根據ON
中指定的條件,去掉那些不符合條件的數據,獲得VT2表,內容以下:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 3 | 9you | | 9you | shanghai | 4 | 9you | | 9you | shanghai | 5 | 9you | | tx | hangzhou | 6 | tx | +-------------+----------+----------+-------------+
VT2就是通過ON
條件篩選之後獲得的有用數據,而接下來的操做將在VT2的基礎上繼續進行。
這一步只有在鏈接類型爲OUTER JOIN
時才發生,如LEFT OUTER JOIN
、RIGHT OUTER JOIN
和FULL OUTER JOIN
。在大多數的時候,咱們都是會省略掉OUTER
關鍵字的,但OUTER
表示的就是外部行的概念。
LEFT OUTER JOIN
把左表記爲保留表,獲得的結果爲:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 3 | 9you | | 9you | shanghai | 4 | 9you | | 9you | shanghai | 5 | 9you | | tx | hangzhou | 6 | tx | | baidu | hangzhou | NULL | NULL | +-------------+----------+----------+-------------+
RIGHT OUTER JOIN
把右表記爲保留表,獲得的結果爲:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 3 | 9you | | 9you | shanghai | 4 | 9you | | 9you | shanghai | 5 | 9you | | tx | hangzhou | 6 | tx | | NULL | NULL | 7 | NULL | +-------------+----------+----------+-------------+
FULL OUTER JOIN
把左右表都做爲保留表,獲得的結果爲:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 3 | 9you | | 9you | shanghai | 4 | 9you | | 9you | shanghai | 5 | 9you | | tx | hangzhou | 6 | tx | | baidu | hangzhou | NULL | NULL | | NULL | NULL | 7 | NULL | +-------------+----------+----------+-------------+
添加外部行的工做就是在VT2表的基礎上添加保留表中被過濾條件過濾掉的數據,非保留表中的數據被賦予NULL值,最後生成虛擬表VT3。
因爲我在準備的測試SQL查詢邏輯語句中使用的是LEFT JOIN
,過濾掉了如下這條數據:
| baidu | hangzhou | NULL | NULL |
如今就把這條數據添加到VT2表中,獲得的VT3表以下:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | 9you | shanghai | 3 | 9you | | 9you | shanghai | 4 | 9you | | 9you | shanghai | 5 | 9you | | tx | hangzhou | 6 | tx | | baidu | hangzhou | NULL | NULL | +-------------+----------+----------+-------------+
接下來的操做都會在該VT3表上進行。
對添加外部行獲得的VT3進行WHERE過濾,只有符合<where_condition>的記錄纔會輸出到虛擬表VT4中。當咱們執行WHERE a.city = 'hangzhou'
的時候,就會獲得如下內容,並存在虛擬表VT4中:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | tx | hangzhou | 6 | tx | | baidu | hangzhou | NULL | NULL | +-------------+----------+----------+-------------+
可是在使用WHERE子句時,須要注意如下兩點:
where_condition=MIN(col)
這類對分組統計的過濾;SELECT city as c FROM t WHERE c='shanghai';
是不容許出現的。GROU BY
子句主要是對使用WHERE
子句獲得的虛擬表進行分組操做。咱們執行測試語句中的GROUP BY a.customer_id
,就會獲得如下內容:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | baidu | hangzhou | NULL | NULL | | tx | hangzhou | 6 | tx | +-------------+----------+----------+-------------+
獲得的內容會存入虛擬表VT5中,此時,咱們就獲得了一個VT5虛擬表,接下來的操做都會在該表上完成。
HAVING
子句主要和GROUP BY
子句配合使用,對分組獲得的VT5虛擬表進行條件過濾。當我執行測試語句中的HAVING count(b.order_id) < 2
時,將獲得如下內容:
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | baidu | hangzhou | NULL | NULL | | tx | hangzhou | 6 | tx | +-------------+----------+----------+-------------+
這就是虛擬表VT6。
如今纔會執行到SELECT
子句,不要覺得SELECT
子句被寫在第一行,就是第一個被執行的。
咱們執行測試語句中的SELECT a.customer_id, COUNT(b.order_id) as total_orders
,從虛擬表VT6中選擇出咱們須要的內容。咱們將獲得如下內容:
+-------------+--------------+ | customer_id | total_orders | +-------------+--------------+ | baidu | 0 | | tx | 1 | +-------------+--------------+
不,尚未完,這只是虛擬表VT7。
若是在查詢中指定了DISTINCT
子句,則會建立一張內存臨時表(若是內存放不下,就須要存放在硬盤了)。這張臨時表的表結構和上一步產生的虛擬表VT7是同樣的,不一樣的是對進行DISTINCT操做的列增長了一個惟一索引,以此來除重複數據。
因爲個人測試SQL語句中並無使用DISTINCT,因此,在該查詢中,這一步不會生成一個虛擬表。
對虛擬表中的內容按照指定的列進行排序,而後返回一個新的虛擬表,咱們執行測試SQL語句中的ORDER BY total_orders DESC
,就會獲得如下內容:
+-------------+--------------+ | customer_id | total_orders | +-------------+--------------+ | tx | 1 | | baidu | 0 | +-------------+--------------+
能夠看到這是對total_orders列進行降序排列的。上述結果會存儲在VT8中。
LIMIT
子句從上一步獲得的VT8虛擬表中選出從指定位置開始的指定行數據。對於沒有應用ORDER BY的LIMIT子句,獲得的結果一樣是無序的,因此,不少時候,咱們都會看到LIMIT子句會和ORDER BY子句一塊兒使用。
MySQL數據庫的LIMIT支持以下形式的選擇:
LIMIT n, m
表示從第n條記錄開始選擇m條記錄。而不少開發人員喜歡使用該語句來解決分頁問題。對於小數據,使用LIMIT子句沒有任何問題,當數據量很是大的時候,使用LIMIT n, m
是很是低效的。由於LIMIT的機制是每次都是從頭開始掃描,若是須要從第60萬行開始,讀取3條數據,就須要先掃描定位到60萬行,而後再進行讀取,而掃描的過程是一個很是低效的過程。因此,對於大數據處理時,是很是有必要在應用層創建必定的緩存機制(貌似如今的大數據處理,都有緩存哦)。
參考文檔: