細節滿滿的 SQL 之的查詢,amazing

子查詢

講子查詢之前,我們先來看看視圖,何謂視圖 ? 視圖是基於 SQL 語句的結果集的可視化的表,包含行和列,就像一個真實的表,但只是一張虛擬表,我們可以將其視作爲一張普通的表;視圖只供數據查詢,不能進行數據更改,也不能保存數據,查詢數據來源於我們的實體表;說的簡單點,視圖就是複雜 SELECT 語句的一個代號,爲查詢提供便利。視圖總是顯示最近的數據,每當我們查詢視圖時,數據庫引擎通過使用 SQL 語句來重建數據。

那何謂子查詢,它與視圖又有何關係 ? 視圖是持久化的 SELECT 語句,而子查詢就是將定義視圖的 SELECT 語句直接用於 FROM 子句當中,它是個一次性的視圖,在 SELECT 語句執行完之後就會消失。光說概念,可能還是不太好理解,我們來看下視圖與子查詢的具體示例,通過示例我們就能更好的理解了

假設我們有如下表


 
CREATE TABLE t_customer_credit ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', login_name VARCHAR(50) NOT NULL COMMENT '登錄名', credit_type TINYINT(1) NOT NULL COMMENT '額度類型,1:自由資金,2:凍結資金,3:優惠', amount DECIMAL(22,6) NOT NULL DEFAULT '0.00000' COMMENT '額度值', create_by VARCHAR(50) NOT NULL COMMENT '創建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創建時間', update_by VARCHAR(50) NOT NULL COMMENT '修改者', PRIMARY KEY (id) ); INSERT INTO `t_customer_credit` VALUES (1, 'zhangsan', 1, 550.000000, 'system', '2019-7-7 11:30:09', '2019-7-8 20:21:05', 'system'); INSERT INTO `t_customer_credit` VALUES (2, 'zhangsan', 2, 0.000000, 'system', '2019-7-7 11:30:09', '2019-7-7 11:30:09', 'system'); INSERT INTO `t_customer_credit` VALUES (3, 'zhangsan', 3, 0.000000, 'system', '2019-7-7 11:30:09', '2019-7-7 11:30:09', 'system'); INSERT INTO `t_customer_credit` VALUES (4, 'lisi', 1, 0.000000, 'system', '2019-7-7 11:30:09', '2019-7-7 11:30:09', 'system'); INSERT INTO `t_customer_credit` VALUES (5, 'lisi', 2, 0.000000, 'system', '2019-7-7 11:30:09', '2019-7-7 11:30:09', 'system'); INSERT INTO `t_customer_credit` VALUES (6, 'lisi', 3, 0.000000, 'system', '2019-7-7 11:30:09', '2019-7-7 11:30:09', 'system');

以及如下 3 個視圖


 
-- 自由資金 DROP VIEW IF EXISTS view_free; CREATE VIEW view_free(login_name, freeAmount) AS SELECT login_name, amount FROM t_customer_credit WHERE credit_type = 1; -- 凍結資金 DROP VIEW IF EXISTS view_freeze; CREATE VIEW view_freeze(login_name, freezeAmount) AS SELECT login_name, amount FROM t_customer_credit WHERE credit_type = 2; -- 優惠 DROP VIEW IF EXISTS view_promotion; CREATE VIEW view_promotion(login_name, promotionAmount) AS SELECT login_name, amount FROM t_customer_credit WHERE credit_type = 3;

那麼我們可以用如下 SQL 來顯示用戶的三個額度


 
SELECT v1.login_name, v1.free_amount, v2.freeze_amount, v3.promotion_amount FROM view_free v1 LEFT JOIN view_freeze v2 ON v1.login_name = v2.login_name LEFT JOIN view_promotion v3 ON v1.login_name = v3.login_name;

 

換成子查詢的方式,SQL 如下


 
SELECT free.login_name, free.freeAmount, freeze.freezeAmount, promotion.promotionAmount FROM ( SELECT login_name, amount freeAmount FROM t_customer_credit WHERE credit_type = 1 ) free LEFT JOIN ( SELECT login_name, amount freezeAmount FROM t_customer_credit WHERE credit_type = 2 ) freeze ON free.login_name = freeze.login_name LEFT JOIN ( SELECT login_name, amount promotionAmount FROM t_customer_credit WHERE credit_type = 3 ) promotion ON free.login_name = promotion.login_name;

注意 SQL 的執行順序,子查詢作爲內層查詢會首先執行;原則上子查詢必須設定名稱,所以我們儘量從處理內容的角度出發爲子查詢設定一個恰當的名稱

普通子查詢

上面講到的子查詢就是普通子查詢,非要給個定義的話,就是返回多行結果的子查詢。這個在實際應用中還是用的非常多的,這個相信大家都比較熟悉,不做過多的說明,只舉個簡單例子

假設我們有商品表:t_commodity


 
DROP TABLE IF EXISTS t_commodity; CREATE TABLE t_commodity ( id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', serial_number VARCHAR(32) NOT NULL COMMENT '編號', name VARCHAR(50) NOT NULL COMMENT '名稱', category VARCHAR(50) NOT NULL COMMENT '類別', sell_unit_price DECIMAL(22,6) NOT NULL COMMENT '出售單價', purchase_unit_price DECIMAL(22,6) NOT NULL COMMENT '進貨單價', create_time DATETIME NOT NULL COMMENT '創建時間', update_time DATETIME NOT NULL COMMENT '更新時間', primary key(id) ) COMMENT '商品表'; -- 初始數據 INSERT INTO t_commodity(serial_number, name, category, sell_unit_price, purchase_unit_price, create_time, update_time) VALUES ('0001', 'T恤衫', '衣服', '80', '20', NOW(), NOW()), ('0002', '羽絨服', '衣服', '280.5', '100', NOW(), NOW()), ('0003', '休閒褲', '褲子', '50', '15.5', NOW(), NOW()), ('0004', '運動短褲', '褲子', '30', '10', NOW(), NOW()), ('0005', '菜刀', '廚具', '35', '10', NOW(), NOW()), ('0006', '鍋鏟', '廚具', '15', '6.5', NOW(), NOW()), ('0007', '鍋', '廚具', '60', '20', NOW(), NOW()), ('0008', '電飯煲', '廚具', '240', '70', NOW(), NOW()), ('0009', '打孔器', '辦公用品', '50', '10.5', NOW(), NOW()), ('0010', '文件架', '辦公用品', '35', '13', NOW(), NOW()), ('0011', '辦公桌', '辦公用品', '280', '120', NOW(), NOW()), ('0012', '辦公椅', '辦公用品', '256', '100', NOW(), NOW());

 

現在我們要實現如下要求:統計出各個類別下商品的數量,我們可以寫出如下 SQL


 
-- 最容易想到的 GROUP BY SELECT category, COUNT(*) cnt FROM t_commodity GROUP BY category -- 子查詢實現,這裏貌似多此一舉,權且當做一個示例 -- 普通子查詢一般應用於多表之間的查詢,比如 學生表、課程表、選課表 之間的一些查詢 SELECT * FROM ( SELECT category, COUNT(*) cnt FROM t_commodity  GROUP BY category ) cs;

標量子查詢

普通子查詢一般是返回多行結果(偶爾也會只返回 1 行,有時也會查不到結果);當返回結果是 1 行 1 列時,該子查詢被稱作標量子查詢,標量子查詢有個特殊的限制,必須而且只能返回 1 行 1 列的結果。

說的簡單點:標量子查詢就是返回單一值的子查詢。由於返回值是單一值,所以標量的查詢可以用在 = 或 <> 這樣需要單一值的比較運算符之中,這也正是其優勢所在。我們來看一些簡單的例子,還是以 t_commodity 爲例,假設我們有如下需求,我們該如何實現它

1、查詢出售單價高於平均出售單價的商品

2、查詢所有商品信息,並在每個商品的信息中加入平均出售單價、平均進貨單價

3、按照商品類別分類,查詢出平均出售單價高於全部商品的平均出售單價的商品類別(類別名、類別平均出售單價)

查詢 1

第一感覺,我們也許會寫出如下的 SQL


 
-- 錯誤的 SQL SELECT * FROM t_commodity WHERE sell_unit_price > AVG(sell_unit_price);

實際上這個 SQL 執行會報錯,WHERE 子句中不能使用聚合函數。那這個查詢要怎麼寫了,此時標量子查詢就派上用場了,SQL 如下


 
-- 查詢出售單價高於平均出售單價的商品 SELECT * FROM t_commodity WHERE sell_unit_price > ( SELECT AVG(sell_unit_price) FROM t_commodity );

查詢 2

這個 SQL 應該比較容易想到,SELECT 子句中加入 平均出售單價、平均進貨單價 列即可,如下


 
-- 查詢所有商品信息,並在每個商品的信息中加入平均出售單價、平均進貨單價 SELECT *, (SELECT AVG(sell_unit_price) FROM t_commodity) avg_sell_price, (SELECT AVG(purchase_unit_price) FROM t_commodity) avg_purchase_price FROM t_commodity;

查詢 3

先以類別進行分組,然後取分組後各個類別的平均出售價格,與全部商品的平均出售價格比較,過濾出滿足條件的類別,SQL 如下


 
-- 按照商品類別分類,查詢出平均出售單價高於全部商品的平均出售單價的商品類別(類別名、類別平均出售單價) SELECT category, AVG(sell_unit_price) category_avg_sell_price FROM t_commodity GROUP BY category HAVING AVG(sell_unit_price) > ( SELECT AVG(sell_unit_price) FROM t_commodity )

 

使用標量子查詢時,我們需要注意一點:我們要明確的知道該子查詢返回的結果就是單一值,絕對不能返回多行結果。不然執行會報錯

關聯子查詢

關聯子查詢是指一個包含對錶的引用的子查詢,該表也顯示在外部查詢中。通俗一點來講,就是子查詢引用到了主查詢的數據數據。在關聯子查詢中,對於外部查詢返回的每一行數據,內部查詢都要執行一次。另外,在關聯子查詢中是信息流是雙向的,外部查詢的每行數據傳遞一個值給子查詢,然後子查詢爲每一行數據執行一次並返回它的記錄。然後,外部查詢根據返回的記錄做出決策。光看概念,晦澀難懂,我們結合具體的例子來看關聯子查詢

還是以商品表:t_commodity 爲例,如何選取出各商品類別中高於該類別平均出售價格的商品,可能大家還沒明白這個需求,那麼我們具體點

所有商品的類別、出售價格如下

 

 

各類別及類別平均出售價格如下

 

 

我們得到的正確結果應該是

 

這個 SQL 我們要如何寫? 像這樣


 
-- 錯誤的 SQL SELECT * FROM t_commodity WHERE sell_unit_price > ( SELECT AVG(sell_unit_price) FROM t_commodity  GROUP BY category )

是肯定不行的,那正確的打開方式應該是怎麼樣的了,此時需要關聯子查詢上場了,SQL如下


 
SELECT * FROM t_commodity t1 WHERE sell_unit_price > ( SELECT AVG(sell_unit_price) FROM t_commodity t2 WHERE t1.category = t2.category  GROUP BY category )

 

子查詢中的 WHERE 子句(WHERE t1.category = t2.category) 至關重要,它的作用是在同一商品類別中對各商品的出售單價與平均單價進行比較。在對錶中某一部分記錄的集合進行比較時,就可以使用關聯子查詢,當出現 “限定” 或 “限制” 這樣的詞彙時,通常會使用關聯子查詢。

在關聯子查詢中,對於外部查詢返回的每一行數據,內部查詢都要執行一次,DBMS 內部的執行結果類似如下

 

總結

1、SQL 執行順序


 
(8) SELECT (9) DISTINCT (11) <TOP_specification> <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) WITH {CUBE | ROLLUP} (7) HAVING <having_condition> (10) ORDER BY <order_by_list>

2、書寫位置

的查詢可以在 SELECT、INSERT、UPDATE 和 DELETE 語句中,同 =、<、>、>=、<=、IN、BETWEEN 等運算符一起使用,使用起來也是非常靈活的;標量子查詢出現的位置就更靈活了,並不僅僅侷限於 WHERE 子句中,通常任何可以使用單一值的位置都可以使用,SELECE 子句、GROUP BY 子句、HAVING 子句、ORDER BY 子句,也就是說,能夠使用常量或者列名的地方,都可以使用標量子查詢。

3、效率問題

子查詢的效率一直都是一個比較頭疼的問題,加合適的索引能改善效率,但也只是侷限於很少的情況;如果數據量大,對性能要求又高,能不用子查詢就儘量別用子查詢,儘量用其他的方式替代,很多情況下,的查詢可以用關聯查詢來替代

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章