神奇的 SQL 之子查詢,細節滿滿 !

前言

  開心一刻數據庫

    有一天,麻雀碰見一隻烏鴉。ide

    麻雀問:你是啥子鳥喲 ?函數

    烏鴉說:我是鳳凰。性能

    麻雀說:哪有你龜兒子這麼黢黑的鳳凰 ?spa

    烏鴉說:你懂個剷剷,老子是燒鍋爐的鳳凰。3d

子查詢

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

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

  假設咱們有以下表教程

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');
View Code

  以及以下 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;
View Code

  那麼咱們能夠用以下 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;
View Code

  注意 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());
View Code

  如今咱們要實現以下要求:統計出各個類別下商品的數量,咱們能夠寫出以下 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;
View Code

標量子查詢

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

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

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

    二、查詢全部商品信息,並在每一個商品的信息中加入平均出售單價、平均進貨單價

    三、按照商品類別分類,查詢出平均出售單價高於所有商品的平均出售單價的商品類別(類別名、類別平均出售單價)

  查詢 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 內部的執行結果相似以下

總結

  一、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>

  二、書寫位置

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

  三、效率問題

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

參考

  《SQL基礎教程》

  《SQL進階教程》

相關文章
相關標籤/搜索