咱們每個人都想要優化SQL語句,以便可以提高性能,可是,若是不瞭解其機制,可能就會事倍功半。我以一個簡單的例子 ,來說解SQL的部分機制。java
今天在公司工做時,面臨這樣一個需求:mysql
根據條件查詢項目的預算金額。sql
查詢要求:數據庫
數據庫有這樣的兩張表,一張是項目表project。項目表有些字段不便展現,於是,只作部分截圖:編程
一張是項目人員表,這張表記錄的是某個項目涉及哪些類型的人員,人員類型(枚舉)以下表所示:編程語言
key值 | value值 |
---|---|
PERSON_TYPE_SALESMAN | 業務員 |
PERSON_TYPE_SALESMAN_MANAGER | 業務部經理 |
PERSON_TYPE_DESIGNER | 設計師 |
PERSON_TYPE_DESIGNER_MANAGER | 設計部經理 |
PERSON_TYPE_PROJECT_SUPERVISION | 工程監理 |
PERSON_TYPE_ENGINEERING_MANAGER | 工程部經理 |
於是,數據表項目人員(project_person)的的設計爲:函數
SELECT SUM(budgetary_amount) FROM zq_project WHERE is_deleted = 0 AND id=167
輸出結果爲 10性能
SELECT SUM(zp.budgetary_amount) FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167
輸出結果爲 60測試
爲何會出現上訴狀況,當咱們在作一對多的sum求和時,就出現了笛卡爾積的現象。咱們查找出項目人員表中的項目編號爲167的有多少條記錄優化
SELECT * from zq_project_person zpp WHERE zpp.is_deleted = 0 and zpp.project_id = 167
輸出結果如圖所示:
由上圖可知,一共有六條記錄,也就是說,項目表中編號爲167的這條記錄對應着項目人員表中的6條記錄,sum以後要計算6次,才變成60,好比下面的代碼:
SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personId FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167;
輸出結果如圖所示:
這就涉及到mysql的執行前後的順序形成笛卡爾積的紊亂
在講解mysql執行的前後順序以前,咱們瞭解一下left join的 on 和 where的區別。
on中的是副表的條件,where會將left join轉化爲inner join格式的數據,這是過濾數據用的。
假設有這兩張表,一張是商品表(goods表),一張是商品分類表(goods_category),商品表的外鍵是商品分類表的主鍵。咱們來作left join的測試
查找語句爲:
SELECT * FROM cce_goods cg LEFT JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0
查找結果如圖所示:
你會發現,編號爲1的商品分類的字段屬性is_deleted的值明明是 1 ,而on以後的is_deleted 的值爲 0 ,這應該是篩選不出來了,但仍是能篩選出來呢?這裏就涉及到on的條件了。
針對第一種狀況
你會發現,這是商品分類的字段屬性是有值的,由於,副表的條件知足了,能拿到副表中的字段屬性值。
若是咱們把left join 改爲inner join ,而cgc.is_deleted = 0 不變,這又不同了,如代碼所示:
SELECT * FROM cce_goods cg INNER JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0
這樣,上面的兩條數據也沒了,由於,inner join 是主表和副表的交集,主表和副表的條件是平行條件,具備一樣的權重,也就是說同時知足主副表的條件,才能出現數據。
再假如,咱們cgc.is_deleted = 0放到外面,如代碼所示:
SELECT * FROM cce_goods cg INNER JOIN cce_goods_category cgc ON(cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0 AND cgc.is_deleted = 0
這樣,也就把left join 隱性成了 inner join了,主表和副表的條件也是平行條件,具備一樣的權重。
二、 咱們來執行如下的查詢語句,如代碼所示:
SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personId FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167;
目前只有三條記錄,其餘的五條記錄沒有展現,這是爲何呢?這個只能意會,沒法言傳。就好比java中的對象,類Project對象是類ProjectPerson的成員屬性,咱們能在ProjectPerson對象裏填充Project對象,但沒法在Project對象中填充ProjectPerson的對象是同樣的道理。
上面也提到了mysql執行的前後順序了,在下面,詳細介紹mysql執行的前後順序。
mysql在執行的過程會有必定的前後順序的,它是按照什麼順序來的呢?
任何一種開發語言,不論是面向結構的c語言,仍是面向對象的JAVA語言,或者,結構化查詢語言sql,其都有一個入口,C語言是main,java是public static void main(String[] args){...},SQL語言好比mysql,其入口是From,而後根據各個優先級。依次往下進行。
以項目表爲主表,以項目人員表和項目進程表爲副表,查找出項目名和項目的預算金額
SELECT DISTINCT zp.id AS projectId, SUM(zp.budgetary_amount) AS totalBugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 GROUP BY zp.id HAVING totalBugAmo <= 12000 ORDER BY totalBugAmo DESC
執行結果如圖所示:
執行順序如圖所示
在講解這個問題前,咱們先看這張圖:
咱們的查語句是:
SELECT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 AND zp.id=167
查詢結果的截圖爲:
你會發現,數據多了,爲何會多?以項目編號爲167的爲研究點,此時,當left join項目人員表時,根據排列組合而來,$C(1,1)*C(2,1)$=2,多生成一張有兩條記錄的虛擬表。此時,再left join項目進程表時,根據排列組合而來,$2* C(3,1)$=6,就會出現,這時就會出現6條數據的虛擬表,這時,咱們再sum的話,就會計算6次,從而得出項目編號爲167的預算金額是60,而不是10。
上面就出現了分組以後的項目編號爲167的預算金額爲90的了,一對多的關係若是sum,是會出現笛卡爾積的錯誤的。
由於,咱們須要使用disdict去重,因而,咱們重寫代碼後爲:
SELECT vt1.projectId, SUM(vt1.bugAmo), vt1.projectName FROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 AND zp.id = 167 ) AS vt1
此時,將其去重後的數據做爲虛擬表,放置在from裏面,咱們拿到的數據就是正確的,如圖所示:
若是,咱們想要查找所有項目的統計金額,也能夠重寫代碼。
重寫代碼的思想:咱們先將查詢結果去重,獲得去重後的虛擬表;再過濾虛擬表的數據,從虛擬表中統計數據,因而乎獲得:
SELECT SUM(vt1.bugAmo) AS toalBugAmo FROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 ) AS vt1 GROUP BY vt1.projectId HAVING toalBugAmo <= 12000 ORDER BY toalBugAmo DESC
這個執行結果爲:
任何一門語言,只要掌握住了,它的機制是怎麼運行的,你也就學會了如何優化,提高該語言的性能等。只要你真正掌握住了一門變成語言,你掌握其餘的編程語言,學起來就很是地快。