SQL表達(dá)式是組成全部或部分SQL語句的字符串,是用在SQL語句中對數(shù)據(jù)進(jìn)行操作、計(jì)算或條件判斷的組合式語句,由常量、列名、運(yùn)算符、函數(shù)等元素組成,可用在查詢過濾、數(shù)據(jù)轉(zhuǎn)換、聚合計(jì)算等場景。下面,我們僅介紹如何使用子查詢優(yōu)化SQL表達(dá)式?我們優(yōu)化SQL表達(dá)式的目標(biāo)是提升查詢效率、簡化邏輯、減少重復(fù)計(jì)算并提高索引利用率。我們使用子查詢優(yōu)化SQL表達(dá)式的思路是將復(fù)雜表達(dá)式拆分為獨(dú)立的子查詢,通過暫存中間結(jié)果、簡化主查詢邏輯,減少重復(fù)計(jì)算或提升索引利用率。具體如下:
一、消除重復(fù)計(jì)算,簡化主查詢
當(dāng)主查詢中多次出現(xiàn)相同的復(fù)雜表達(dá)式(如:計(jì)算、函數(shù)調(diào)用)時(shí),我們可將其放入子查詢中一次性計(jì)算,主查詢直接引用結(jié)果,避免重復(fù)運(yùn)算。
場景:計(jì)算訂單表中“金額(amount)+ 稅費(fèi)(tax)”的總和,并篩選出該總和大于1000的訂單,同時(shí)在結(jié)果中顯示該總和。
優(yōu)化前(重復(fù)計(jì)算):
SELECT
order_id,
amount + tax AS total, -- 第一次計(jì)算:金額加稅費(fèi)
(amount + tax) * 0.05 AS fee, -- 第二次計(jì)算:相同表達(dá)式用于計(jì)算手續(xù)費(fèi)
(amount + tax) * 1.1 AS total_with_service -- 第三次計(jì)算:相同表達(dá)式用于計(jì)算含服務(wù)費(fèi)總額
FROM orders
WHERE (amount + tax) > 1000; -- 第四次計(jì)算:相同表達(dá)式用于過濾條件
問題所在:amount + tax
被重復(fù)計(jì)算4次,不僅低效,且修改時(shí)需同步更新所有位置,維護(hù)成本高。
優(yōu)化后(子查詢暫存結(jié)果):
-- 子查詢計(jì)算一次total,主查詢直接引用
SELECT
order_id,
total, -- 直接使用子查詢結(jié)果
total * 0.05 AS fee, -- 基于子查詢結(jié)果計(jì)算
total * 1.1 AS total_with_service -- 基于子查詢結(jié)果計(jì)算
FROM (
SELECT
order_id,
amount + tax AS total -- 僅計(jì)算一次:金額加稅費(fèi)
FROM orders
) AS sub -- 子查詢必須命名(此處為sub)
WHERE total > 1000; -- 直接使用子查詢結(jié)果過濾
優(yōu)化說明:子查詢中amount + tax
僅計(jì)算一次,主查詢通過別名total
直接引用,減少重復(fù)運(yùn)算,且邏輯更清晰。
再舉一例:庫存成本計(jì)算優(yōu)化
-- 優(yōu)化前:復(fù)雜表達(dá)式在SELECT和WHERE中重復(fù)出現(xiàn)
SELECT
product_id,
product_name,
-- 復(fù)雜計(jì)算:成本*數(shù)量 + 運(yùn)輸費(fèi)*折扣(重復(fù)3次)
(cost * quantity + shipping_fee * discount) AS total_cost,
-- 基于復(fù)雜計(jì)算的二次運(yùn)算
(cost * quantity + shipping_fee * discount) * 1.1 AS total_with_tax,
-- 基于復(fù)雜計(jì)算的三次運(yùn)算
(cost * quantity + shipping_fee * discount) * 0.02 AS service_fee
FROM inventory
-- 重復(fù)使用相同的復(fù)雜計(jì)算作為過濾條件
WHERE (cost * quantity + shipping_fee * discount) > 1000
AND category = 'electronics';
-- 優(yōu)化后:將復(fù)雜表達(dá)式放入子查詢,只計(jì)算一次
SELECT
product_id,
product_name,
total_cost, -- 直接引用子查詢結(jié)果
total_cost * 1.1 AS total_with_tax, -- 基于子查詢結(jié)果計(jì)算
total_cost * 0.02 AS service_fee -- 基于子查詢結(jié)果計(jì)算
FROM (
-- 子查詢:一次性計(jì)算復(fù)雜表達(dá)式并命名為total_cost
SELECT
product_id,
product_name,
cost,
quantity,
shipping_fee,
discount,
(cost * quantity + shipping_fee * discount) AS total_cost
FROM inventory
WHERE category = 'electronics' -- 提前過濾類別,減少數(shù)據(jù)量
) AS subquery
-- 直接使用子查詢中已計(jì)算好的total_cost進(jìn)行過濾
WHERE total_cost > 1000;
優(yōu)化說明:
1、我們將重復(fù)出現(xiàn)的(cost * quantity + shipping_fee * discount)
復(fù)雜表達(dá)式移至子查詢,僅計(jì)算一次
2、子查詢中提前過濾category = 'electronics'
,減少主查詢需要處理的數(shù)據(jù)量
3、主查詢直接引用子查詢中計(jì)算好的total_cost
,避免了多次重復(fù)計(jì)算
4、邏輯更清晰,若需修改計(jì)算邏輯,只需在子查詢中修改一次即可
二、將復(fù)雜條件拆分,提升索引利用率
當(dāng)過濾條件包含復(fù)雜表達(dá)式(如:函數(shù)、多列運(yùn)算)時(shí),直接寫在WHERE
中可能導(dǎo)致索引失效,具體說:直接對索引列使用函數(shù)(如:YEAR(register_time))會導(dǎo)致:索引失效(數(shù)據(jù)庫無法使用B+樹定位);強(qiáng)制全表掃描(性能災(zāi)難)。用子查詢拆分后,可讓主查詢的過濾條件更簡單,便于利用索引。
場景:我們查詢用戶表中“注冊年份(register_time的年份)為2023”的用戶,register_time
字段有索引。
優(yōu)化前(索引失效):
-- YEAR(register_time)是函數(shù),導(dǎo)致register_time的索引無法使用
-- 數(shù)據(jù)庫需全表掃描并對每條記錄執(zhí)行函數(shù)計(jì)算,效率低
SELECT user_id, register_time
FROM users
WHERE YEAR(register_time) = 2023; -- 索引列被函數(shù)包裹
優(yōu)化后(子查詢拆分,利用索引):
-- 子查詢先通過索引篩選時(shí)間范圍,再提取年份(避免函數(shù)直接作用于索引列)
SELECT user_id, register_time
FROM (
-- 子查詢用范圍條件,可利用register_time的索引快速定位
SELECT user_id, register_time
FROM users
WHERE register_time BETWEEN '2023-01-01' AND '2023-12-31 23:59:59' -- 范圍查詢用索引
) AS sub
WHERE YEAR(register_time) = 2023; -- 過濾邊緣數(shù)據(jù),進(jìn)一步精確篩選(處理極端時(shí)間格式)
優(yōu)化說明:子查詢的BETWEEN
條件可直接使用register_time
的索引,效率遠(yuǎn)高于主查詢中直接對索引列使用函數(shù)。
三、用關(guān)聯(lián)子查詢簡化多表復(fù)雜邏輯
當(dāng)我們需要基于另一張表的條件篩選數(shù)據(jù)時(shí),關(guān)聯(lián)子查詢可替代復(fù)雜的多表連接+表達(dá)式,讓邏輯更清晰。
場景1:我們查詢“訂單金額大于該用戶平均訂單金額”的訂單,涉及orders
表(訂單)和users
表(用戶)。
優(yōu)化前(復(fù)雜連接+表達(dá)式):顯式JOIN示例
SELECT o.order_id, o.user_id, o.amount
FROM orders o
-- 先計(jì)算每個(gè)用戶的平均訂單金額,再通過JOIN關(guān)聯(lián)
JOIN ( -- 臨時(shí)聚合表
SELECT user_id, AVG(amount) AS avg_amount
FROM orders
GROUP BY user_id
) AS avg_o ON o.user_id = avg_o.user_id
WHERE o.amount > avg_o.avg_amount; -- 主查詢中用連接結(jié)果判斷
優(yōu)化后(關(guān)聯(lián)子查詢):
SELECT order_id, user_id, amount
FROM orders o
WHERE amount > (
-- 關(guān)聯(lián)子查詢:直接計(jì)算當(dāng)前訂單所屬用戶的平均金額
SELECT AVG(amount)
FROM orders
WHERE user_id = o.user_id -- 關(guān)聯(lián)外部表orders的user_id,關(guān)聯(lián)當(dāng)前用戶
);
優(yōu)化說明:關(guān)聯(lián)子查詢通過WHERE user_id = o.user_id
直接關(guān)聯(lián)外部表,避免了顯式連接,邏輯更簡潔,且數(shù)據(jù)庫通常會優(yōu)化這類子查詢的執(zhí)行效率。
場景2(多表關(guān)聯(lián)深化):我們查詢“購買過至少3件商品且訂單金額超過該商品類別平均價(jià)格”的訂單詳情。
優(yōu)化前(多層連接):
SELECT o.order_id, o.product_id, o.quantity, o.amount
FROM orders o
JOIN products p ON o.product_id = p.product_id
JOIN (
-- 計(jì)算每個(gè)類別的平均價(jià)格
SELECT category_id, AVG(price) AS avg_category_price
FROM products
GROUP BY category_id
) AS avg_p ON p.category_id = avg_p.category_id
WHERE o.quantity >= 3 -- 購買數(shù)量條件
AND o.amount > avg_p.avg_category_price; -- 金額超過類別均價(jià)
優(yōu)化后(關(guān)聯(lián)子查詢):
SELECT order_id, product_id, quantity, amount
FROM orders o
WHERE quantity >= 3 -- 先過濾數(shù)量條件
AND amount > (
-- 子查詢:獲取當(dāng)前商品所屬類別的平均價(jià)格
SELECT AVG(price)
FROM products p1
WHERE p1.category_id = (
-- 嵌套子查詢:獲取當(dāng)前商品的類別ID
SELECT category_id
FROM products p2
WHERE p2.product_id = o.product_id
)
);
優(yōu)化說明:通過嵌套關(guān)聯(lián)子查詢,逐層獲取所需信息,避免了多表顯式連接,邏輯層次更清晰。
四、使用子查詢優(yōu)化SQL表達(dá)式語法模板
1、基礎(chǔ)子查詢(用于暫存中間結(jié)果)
-- 主查詢:引用子查詢的結(jié)果進(jìn)行篩選或計(jì)算
SELECT 主查詢字段
FROM (
-- 子查詢:計(jì)算或篩選基礎(chǔ)數(shù)據(jù)
SELECT
字段1,
字段2,
-- 復(fù)雜表達(dá)式僅計(jì)算一次
(復(fù)雜表達(dá)式) AS 別名
FROM 表名
WHERE 子查詢過濾條件 -- 可選:提前過濾部分?jǐn)?shù)據(jù)
) AS 子查詢別名 -- 子查詢必須有別名
WHERE 主查詢過濾條件; -- 使用子查詢的別名簡化條件
2、關(guān)聯(lián)子查詢(引用外部表字段)
SELECT 外部表字段
FROM 外部表 外部表別名
WHERE 外部表字段 比較運(yùn)算符 (
-- 關(guān)聯(lián)子查詢:通過WHERE條件與外部表關(guān)聯(lián)
SELECT 聚合函數(shù)(內(nèi)部表字段) -- 通常用于聚合計(jì)算
FROM 內(nèi)部表 內(nèi)部表別名
WHERE 內(nèi)部表字段 = 外部表別名.外部表字段 -- 關(guān)聯(lián)條件
GROUP BY 分組字段 -- 可選:按條件分組計(jì)算
);
3、子查詢在SELECT列表中(用于補(bǔ)充字段)
SELECT
主表字段,
-- 子查詢作為字段:為每條記錄補(bǔ)充額外計(jì)算結(jié)果
(SELECT 子查詢字段
FROM 子查詢表
WHERE 子查詢表關(guān)聯(lián)字段 = 主表.主表關(guān)聯(lián)字段) AS 補(bǔ)充字段別名
FROM 主表
WHERE 主查詢條件;
示例:查詢每個(gè)用戶的最新訂單ID
SELECT
user_id,
-- 子查詢:獲取當(dāng)前用戶的最新訂單ID
(SELECT order_id
FROM orders
WHERE user_id = u.user_id
ORDER BY create_time DESC
LIMIT 1) AS latest_order_id
FROM users u;
總結(jié):
子查詢優(yōu)化SQL表達(dá)式的主要方式:
1、暫存中間結(jié)果:減少重復(fù)計(jì)算,尤其適合多次出現(xiàn)的復(fù)雜表達(dá)式;
2、拆分復(fù)雜條件:讓過濾條件更簡單,便于數(shù)據(jù)庫使用索引;
3、簡化關(guān)聯(lián)邏輯:用關(guān)聯(lián)子查詢替代復(fù)雜連接,提升可讀性和執(zhí)行效率。
除此之外,在子查詢中優(yōu)先過濾無關(guān)數(shù)據(jù)(如:固定條件、非核心字段篩選),減少主查詢需要處理的記錄數(shù),能間接優(yōu)化表達(dá)式計(jì)算效率。示例:我們計(jì)算“電子類商品庫存成本”時(shí),子查詢先通過WHERE category = 'electronics'
過濾非電子類商品,再計(jì)算成本,減少主查詢的處理數(shù)據(jù)量。我們還可以合理使用子查詢位置,優(yōu)化字段補(bǔ)充邏輯:將子查詢嵌入SELECT
列表,為每條記錄補(bǔ)充額外計(jì)算結(jié)果(如:關(guān)聯(lián)表的最新數(shù)據(jù)),避免復(fù)雜的JOIN
+GROUP BY
操作。示例:查詢用戶列表時(shí),通過(SELECT order_id FROM orders WHERE user_id = u.user_id ORDER BY create_time DESC LIMIT 1)
獲取每個(gè)用戶的最新訂單ID。
需注意:實(shí)際優(yōu)化時(shí),我們需結(jié)合執(zhí)行計(jì)劃(EXPLAIN
)分析索引使用和數(shù)據(jù)掃描情況,避免子查詢嵌套過深導(dǎo)致的性能問題。子查詢不宜嵌套過深(通常建議不超過3層),否則可能影響性能,此時(shí)我們可考慮臨時(shí)表或CTE(公用表表達(dá)式)進(jìn)一步優(yōu)化。同時(shí),并非所有場景都適合子查詢,我們需結(jié)合實(shí)際數(shù)據(jù)量和執(zhí)行計(jì)劃選擇最優(yōu)方案。
閱讀原文:原文鏈接
該文章在 2025/9/1 11:12:09 編輯過