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

一條SQL語句的千迴百轉

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

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

首先看一下示例語句

image

它的執行順序是這樣的

image

準備資料

我們會先準備一些資料,即建立 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。

image

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的解析之旅就結束了,最後用一張圖總結一下今天的內容:

image

參考:MySQL 5.7 Reference Manual