1. 程式人生 > >一條SQL語句的千回百轉

一條SQL語句的千回百轉

有一個 默認 結構化查詢語言 沒有 out 過程 列表表達式 虛擬表 ota

SQL語言相信大家都不陌生,從本質上來說,它是一種結構化查詢語言,是用來數據庫之間的通信的編程語言。作為一名Java程序員,我們從Java角度來看,SQL語言相當於Java接口,而數據庫是實現這個接口的實現類,SQL語句則是實現類的方法!!。從這裏我們就可以理解了,每個數據庫都有著自己獨特的規則,但大體上是遵循SQL標準。

SQL 語句有一個讓大部分人都感到困惑的地方,就是我寫的 SQL 語句的跟我預想要的結果不一樣。在這裏,我們就以 Mysql 數據庫為例,對一條 SQL 語句的執行順序進行分析。

首先看一下示例語句

技術分享圖片

它的執行順序是這樣的

技術分享圖片

準備數據

我們會先準備一些數據,即創建 classes、student 表,並插入測試數據, SQL語句如下:

DROP TABLE classes;
CREATE TABLE classes (class_id varchar(10), class_name varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci;
INSERT INTO classes (class_id, class_name) VALUES (‘2‘, ‘2班‘);
INSERT INTO classes (class_id, class_name) VALUES (‘1‘, ‘1班‘);

DROP TABLE student;
CREATE TABLE student (stu_name varchar(10), class_id varchar(10), stu_id varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci;

INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘王五‘, ‘2‘, ‘124‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘王五‘, ‘2‘, ‘123‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘李四‘, ‘2‘, ‘122‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘李四‘, ‘1‘, ‘114‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘張三‘, ‘1‘, ‘112‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘張三‘, ‘2‘, ‘121‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘李四‘, ‘1‘, ‘113‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘張三‘, ‘1‘, ‘111‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘小紅‘, ‘‘, ‘141‘);
INSERT INTO student (stu_name, class_id, stu_id) VALUES (‘王五‘, ‘2‘, ‘125‘);

OK,有了數據之後,我們就可以來看看 SQL 語句在 MySQL 中執行過程了,SQL 語句如下:

select stu_name as name,count(stu_name) total from student s left join classes c on s.class_id=c.class_id where stu_name in(‘張三‘,‘王五‘,‘李四‘) group by name HAVING count(stu_name)>2 order by stu_id desc limit 1;

SQL執行之旅

可能你現在還對 Mysql 語句的執行順序一知半解,沒關系,下來我將按 SQL 執行的順序詳細介紹每個關鍵字的作用,以及註意的地方。

1、FROM:對FROM子句中的前兩個表執行笛卡爾積(交叉聯接),生成虛擬表VT1。

技術分享圖片

2、ON:對VT1應用ON篩選器,只有那些使為真才被插入到VT2。ON不能單獨,在這裏你可以把ON理解為WHERE。

技術分享圖片

3、JOIN:如果指定了OUTER JOIN(相對於CROSS JOIN或INNER JOIN),保留表(主表)中不符合ON條件匹配的行將作為外部行添加到VT2,生成VT3。如果FROM子句超過兩個表,上一個聯接生成的結果表會和下一個表重復執行步驟1到步驟3,直到處理完所有的表的關聯。

技術分享圖片

4、WHERE:對VT3應用WHERE篩選器,只有為true的行才插入VT4。

技術分享圖片

5、GROUP BY:按GROUP BY子句中的列列表對VT4中的行進行分組,生成VT5。

技術分享圖片

在這裏會有一個奇怪的現象,MySQL執行順序GROUP BY -> HAVING -> SELECT,從順序看SELECL在GROUP BY之後,GROUP BY 應該不可以使用SELECT字段別名,但是在GROUP BY卻可以使用SELECT字段別名,主要原因MySQL擴展了標準SQL,允許GROUP BY子句使用的SELECT子句中的別名以及和非列表表達式等標準, 並認為語句是有效的。
從MySQL 5.7.5開始,默認SQL mode模式包括 ONLY_FULL_GROUP_BY。(在5.7.5之前,MySQL不檢測功能依賴性,ONLY_FULL_GROUP_BY默認情況下不啟用。更多請參考:MySQL 5.7 Reference Manual-GROUP BY

6、HAVING :對VT5應用HAVING篩選器,只有為true的組插入到VT6。

技術分享圖片

HAVING同GROUP BY一樣,MySQL拓展SQL標準以允許HAVING可以使用別名和非列表表達式

7、SELECT:將VT6每一組數據執行select xx,有幾組就執行幾次,產生VT7。

技術分享圖片

這裏有一點要註意,當SQL mode 模式ONLY_FULL_GROUP_BY不開啟,不會強制SELECT指定的字段必須屬於GROUP BY後的條件。若符合條件的字段有多個,則只顯示第一次出現的字段。雖然這種查詢在語法上通過了,但結果並沒有什麽意義,因為其他字段並非需要的準確值。所以最好select語句指定的字段必須是“分組依據字段”。

8、ORDER BY:將VT7中的行按ORDER BY子句中的列列表順序,ORDER BY只能選擇SELECT的字段

技術分享圖片

9、LIMIT:從VT7的開始處選擇指定數量或比例的行,生成表VT8,並返回給調用者。

技術分享圖片

OK,到這裏就執行結束了。我們可以發現,SQL 語句的語法順序和執行順序並不一致,如果你已經可以清醒知道它們之間差異,你就可以看出為什麽以前寫的SQL總是和我們預想的不一致。你看,哪怕只有一條小小的 SQL 語句都有這麽多門道,只有不斷專研探究,我們才可以真正掌握這一門技術。

這裏多提一下,在SQL語法有幾點要特別註意,SELECT雖然在GROUP BY和HAVING 之後,但是如果SQL mode模式 ONLY_FULL_GROUP_BY不開啟,GROUP BY和HAVING是允許使用SELECT的字段,而且也不會強制SELECT指定的字段必須屬於GROUP BY後的條件。至此SQL的解析之旅就結束了,最後用一張圖總結一下今天的內容:

技術分享圖片

參考:MySQL 5.7 Reference Manual

一條SQL語句的千回百轉