在資料庫應用開發中,撰寫高效且正確的 SQL 查詢是每位開發者必須掌握的核心技能。隨著資料量的增長和業務邏輯的複雜化,我們需要運用各種查詢技巧來解決實際問題。從控制結果集大小的 LIMIT 子句,到合併多個查詢結果的 UNION 運算,從關聯多個表格的 JOIN 操作,到暫存中間結果的臨時表應用,這些技術共同構成了資料庫查詢的完整工具箱。
本文將從實務角度出發,深入探討各種查詢技巧的語法、特性和應用場景。我們不僅會介紹這些技術的基本用法,還會討論它們的效能特性和最佳化策略。透過具體的案例和程式碼範例,讀者可以了解如何在實際專案中選擇和組合這些技巧,以達成高效且可維護的資料庫查詢。
LIMIT 與 OFFSET 分頁查詢
在許多應用場景中,我們並不需要一次取得所有的查詢結果。例如,在網頁應用中顯示搜尋結果時,通常會採用分頁方式,每頁只顯示固定數量的記錄。LIMIT 子句正是為這類需求而設計,它能夠限制查詢傳回的記錄數量。
基本的 LIMIT 用法
LIMIT 子句通常放在查詢語句的最後,指定要傳回的最大記錄數:
-- 查詢葡萄酒比賽的前三名
-- 使用 LIMIT 限制只傳回三筆記錄
SELECT
wine_id,
wine_name,
winery_name,
score,
ranking
FROM wine_competition_result
ORDER BY ranking ASC -- 先按排名排序
LIMIT 3; -- 只取前三名
這個查詢會傳回排名前三的葡萄酒記錄。ORDER BY 子句確保結果按照排名順序排列,LIMIT 3 則限制只傳回前三筆。
-- 查詢最新的五筆訂單
SELECT
order_id,
customer_id,
order_date,
total_amount,
status
FROM orders
ORDER BY order_date DESC -- 按日期降冪排序
LIMIT 5; -- 只取最新五筆
LIMIT 與 OFFSET 的分頁應用
當我們需要實作分頁功能時,除了 LIMIT 之外,還需要 OFFSET 來指定要跳過的記錄數。OFFSET 告訴資料庫從第幾筆記錄開始傳回:
-- 分頁查詢:取得第二頁的資料(每頁 10 筆)
-- OFFSET 10 表示跳過前 10 筆記錄
SELECT
product_id,
product_name,
price,
category
FROM products
ORDER BY product_id
LIMIT 10 OFFSET 10; -- 跳過前 10 筆,傳回接下來的 10 筆
在實際應用中,分頁的計算公式如下:
-- 通用分頁公式
-- page_number: 頁碼(從 1 開始)
-- page_size: 每頁記錄數
-- OFFSET = (page_number - 1) * page_size
-- 第 1 頁:LIMIT 10 OFFSET 0
-- 第 2 頁:LIMIT 10 OFFSET 10
-- 第 3 頁:LIMIT 10 OFFSET 20
-- 查詢第 5 頁的產品(每頁 20 筆)
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 80; -- (5-1) * 20 = 80
分頁查詢的效能考量
雖然 LIMIT 和 OFFSET 提供了簡單的分頁機制,但在處理大量資料時可能會遇到效能問題。當 OFFSET 值很大時,資料庫仍然需要掃描並跳過這些記錄,這會導致查詢效率降低:
-- 效能較差的大 OFFSET 查詢
-- 資料庫需要掃描前 100000 筆記錄後才開始傳回結果
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 100000;
-- 更有效率的做法:使用 keyset 分頁
-- 假設上一頁最後一筆的 product_id 是 100020
SELECT product_id, product_name, price
FROM products
WHERE product_id > 100020 -- 從上一頁最後一筆之後開始
ORDER BY product_id
LIMIT 20;
Keyset 分頁(也稱為游標分頁或 seek method)利用索引直接定位到起始位置,效能遠優於傳統的 OFFSET 分頁。但它需要在應用程式中追蹤上一頁的最後一筆記錄。
不同資料庫的語法差異
LIMIT 的語法在不同資料庫系統中略有差異:
-- MySQL 和 PostgreSQL 的語法
SELECT * FROM products LIMIT 10 OFFSET 20;
-- 或者
SELECT * FROM products LIMIT 20, 10; -- MySQL 特有語法:LIMIT offset, count
-- SQL Server 的語法
SELECT TOP 10 * FROM products;
-- 或者(SQL Server 2012+)
SELECT * FROM products
ORDER BY product_id
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Oracle 的語法(12c+)
SELECT * FROM products
FETCH FIRST 10 ROWS ONLY;
-- 或者使用 ROWNUM(較舊版本)
SELECT * FROM (
SELECT p.*, ROWNUM rn FROM products p WHERE ROWNUM <= 30
) WHERE rn > 20;
UNION 與 UNION ALL 集合運算
當我們需要合併多個查詢的結果時,可以使用 UNION 或 UNION ALL 運算子。這兩個運算子都能將多個 SELECT 語句的結果合併成一個結果集,但它們在處理重複記錄方面有所不同。
UNION 的基本用法
UNION 會自動移除合併結果中的重複記錄,只保留唯一的值:
-- 合併兩個表中的葡萄酒名稱
-- UNION 會自動移除重複的記錄
SELECT wine_type_name AS wine_name FROM wine_type
UNION
SELECT wine_name FROM wine_competition_result;
假設 wine_type 表包含 Chardonnay、Cabernet Sauvignon、Merlot,而 wine_competition_result 表包含 Riesling、Merlot、Pinot Grigio,UNION 的結果會是六筆唯一的記錄(Merlot 只出現一次)。
UNION ALL 保留所有記錄
如果需要保留所有記錄,包括重複的部分,應該使用 UNION ALL:
-- 使用 UNION ALL 保留所有記錄
-- 不會移除重複值
SELECT wine_type_name AS wine_name FROM wine_type
UNION ALL
SELECT wine_name FROM wine_competition_result;
使用同樣的範例資料,UNION ALL 的結果會是七筆記錄(Merlot 出現兩次)。
UNION 的使用規則
使用 UNION 時需要遵守以下規則:
-- 規則 1:欄位數量必須相同
-- 正確:兩個查詢都選取兩個欄位
SELECT product_id, product_name FROM products_2023
UNION
SELECT product_id, product_name FROM products_2024;
-- 錯誤:欄位數量不同
-- SELECT product_id, product_name, price FROM products_2023
-- UNION
-- SELECT product_id, product_name FROM products_2024; -- 會產生錯誤
-- 規則 2:對應欄位的資料類型必須相容
-- 正確:整數與整數、字串與字串相容
SELECT order_id, order_date FROM online_orders
UNION
SELECT order_id, order_date FROM store_orders;
-- 規則 3:結果集的欄位名稱取自第一個查詢
-- 可以使用別名來指定自訂的欄位名稱
SELECT product_name AS item_name, price FROM electronics
UNION
SELECT product_name, price FROM clothing;
實際應用案例
讓我們看一些 UNION 的實際應用場景:
-- 案例 1:合併多個年度的銷售記錄
SELECT
'Q1' AS quarter,
product_id,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount
FROM sales_q1_2024
GROUP BY product_id
UNION ALL
SELECT
'Q2' AS quarter,
product_id,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount
FROM sales_q2_2024
GROUP BY product_id
ORDER BY product_id, quarter;
-- 案例 2:整合不同來源的客戶資料
SELECT
customer_id,
customer_name,
email,
'Website' AS source
FROM website_customers
UNION
SELECT
customer_id,
customer_name,
email,
'Mobile App' AS source
FROM mobile_app_customers
ORDER BY customer_name;
-- 案例 3:建立下拉選單的選項清單
SELECT
'' AS code,
'-- 請選擇國家 --' AS display_name,
0 AS sort_order -- 確保空選項在最前面
UNION ALL
SELECT
country_code AS code,
country_name AS display_name,
1 AS sort_order
FROM countries
WHERE is_active = TRUE
ORDER BY sort_order, display_name;
UNION 與其他集合運算子
除了 UNION,SQL 還提供了其他集合運算子:
-- INTERSECT:取得兩個查詢的交集
-- 傳回同時出現在兩個結果集中的記錄
SELECT customer_id FROM premium_members
INTERSECT
SELECT customer_id FROM newsletter_subscribers;
-- EXCEPT(或 MINUS):取得差集
-- 傳回在第一個結果集但不在第二個結果集中的記錄
SELECT product_id FROM all_products
EXCEPT
SELECT product_id FROM discontinued_products;
需要注意的是,INTERSECT 和 EXCEPT 並非所有資料庫都支援。MySQL 在 8.0.31 版本之前不支援這兩個運算子,需要使用其他方式來達成相同的效果:
-- 在 MySQL 中模擬 INTERSECT
SELECT DISTINCT g.snack_name
FROM good_snack g
INNER JOIN bad_snack b ON g.snack_name = b.snack_name;
-- 在 MySQL 中模擬 EXCEPT
SELECT g.snack_name
FROM good_snack g
WHERE g.snack_name NOT IN (
SELECT snack_name FROM bad_snack
);
多表 JOIN 關聯查詢
在正規化的資料庫設計中,資料通常分散儲存於多個相關聯的表格。JOIN 操作讓我們能夠根據表格之間的關聯條件,將這些分散的資料組合起來。本節將透過實際案例,說明如何運用 JOIN 來進行多表查詢。
基本的多表 JOIN
讓我們以一個旅遊資料庫為例,包含省份、首府城市和旅遊景點三個表格:
-- 資料表結構
-- province: province_id, province_name, official_language
-- capital_city: city_id, city_name, province_id, population
-- tourist_attraction: attraction_id, attraction_name, province_id, annual_visitors
-- 查詢省份、首府和旅遊景點的完整資訊
SELECT
p.province_name, -- 省份名稱
p.official_language, -- 官方語言
cc.city_name AS capital_name, -- 首府名稱
cc.population, -- 首府人口
ta.attraction_name, -- 景點名稱
ta.annual_visitors -- 年遊客數
FROM province p
INNER JOIN capital_city cc
ON p.province_id = cc.province_id
INNER JOIN tourist_attraction ta
ON p.province_id = ta.province_id
ORDER BY p.province_name, ta.annual_visitors DESC;
這個查詢透過 province_id 欄位,將三個表格關聯起來。每個省份可能有多個旅遊景點,所以結果中同一個省份可能會出現多次。
使用別名簡化查詢
當查詢涉及多個表格時,使用表格別名可以讓程式碼更簡潔易讀:
-- 使用有意義的別名
SELECT
w.winery_name,
w.founded_year,
va.area_name,
r.region_name,
c.country_name,
wt.wine_type_name,
p.annual_production
FROM winery w
INNER JOIN viticultural_area va ON w.area_id = va.area_id
INNER JOIN region r ON va.region_id = r.region_id
INNER JOIN country c ON r.country_id = c.country_id
INNER JOIN portfolio p ON w.winery_id = p.winery_id
INNER JOIN wine_type wt ON p.wine_type_id = wt.wine_type_id
WHERE p.annual_production > 10000
ORDER BY c.country_name, r.region_name, w.winery_name;
結合 JOIN 與篩選條件
在實際應用中,我們經常需要在 JOIN 的基礎上加入各種篩選條件:
-- 查詢提供導覽且當季有生產 Merlot 的酒莊
SELECT
w.winery_name,
w.founded_year,
va.area_name,
wt.wine_type_name
FROM winery w
INNER JOIN viticultural_area va
ON w.area_id = va.area_id
INNER JOIN portfolio p
ON w.winery_id = p.winery_id
INNER JOIN wine_type wt
ON p.wine_type_id = wt.wine_type_id
WHERE w.offers_tours = TRUE -- 提供導覽
AND p.in_season = TRUE -- 當季生產
AND wt.wine_type_name = 'Merlot' -- 酒種為 Merlot
ORDER BY va.area_name, w.winery_name;
使用 LEFT JOIN 處理可選關聯
當某些關聯可能不存在時,應該使用 LEFT JOIN 來保留主表的所有記錄:
-- 查詢所有省份及其旅遊景點(包括沒有景點的省份)
SELECT
p.province_name,
p.official_language,
COALESCE(ta.attraction_name, '尚無登錄景點') AS attraction_name,
COALESCE(ta.annual_visitors, 0) AS annual_visitors
FROM province p
LEFT JOIN tourist_attraction ta
ON p.province_id = ta.province_id
ORDER BY p.province_name, ta.annual_visitors DESC;
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
rectangle "多表 JOIN 查詢結構" as title
rectangle "主表" as main {
rectangle "province"
}
rectangle "關聯表" as related {
rectangle "capital_city"
rectangle "tourist_attraction"
}
rectangle "JOIN 條件" as conditions {
rectangle "province_id = province_id"
}
main -[hidden]down-> related
related -[hidden]down-> conditions
note bottom of main
起始點
包含所有待查詢的主要記錄
end note
note bottom of related
透過外鍵關聯
可能是一對一或一對多
end note
@enduml
臨時表的建立與應用
臨時表是一種特殊的表格,它只在當前資料庫連線(session)期間存在,連線結束後會自動刪除。臨時表非常適合用來儲存中間結果,簡化複雜的查詢邏輯。
建立臨時表
建立臨時表有兩種主要方式:直接定義結構,或根據查詢結果建立。
-- 方式 1:直接定義臨時表結構
CREATE TEMPORARY TABLE temp_winery_summary (
winery_id INT PRIMARY KEY,
winery_name VARCHAR(200),
area_name VARCHAR(100),
wine_count INT,
total_production INT
);
-- 之後可以插入資料
INSERT INTO temp_winery_summary
SELECT
w.winery_id,
w.winery_name,
va.area_name,
COUNT(p.wine_type_id) AS wine_count,
SUM(p.annual_production) AS total_production
FROM winery w
INNER JOIN viticultural_area va ON w.area_id = va.area_id
INNER JOIN portfolio p ON w.winery_id = p.winery_id
GROUP BY w.winery_id, w.winery_name, va.area_name;
-- 方式 2:根據查詢結果直接建立臨時表
-- 結構會自動根據查詢結果推導
CREATE TEMPORARY TABLE temp_touring_merlot_wineries AS
SELECT
w.winery_id,
w.winery_name,
va.area_name,
va.area_id
FROM winery w
INNER JOIN viticultural_area va
ON w.area_id = va.area_id
INNER JOIN portfolio p
ON w.winery_id = p.winery_id
INNER JOIN wine_type wt
ON p.wine_type_id = wt.wine_type_id
WHERE w.offers_tours = TRUE
AND p.in_season = TRUE
AND wt.wine_type_name = 'Merlot';
使用臨時表
建立臨時表後,可以像使用一般表格一樣查詢它:
-- 查詢臨時表的內容
SELECT * FROM temp_touring_merlot_wineries
ORDER BY area_name, winery_name;
-- 將臨時表與其他表格進行 JOIN
SELECT
t.winery_name,
t.area_name,
r.region_name,
c.country_name
FROM temp_touring_merlot_wineries t
INNER JOIN viticultural_area va ON t.area_id = va.area_id
INNER JOIN region r ON va.region_id = r.region_id
INNER JOIN country c ON r.country_id = c.country_id
ORDER BY c.country_name, r.region_name;
臨時表的應用場景
臨時表在以下場景中特別有用:
-- 場景 1:分步驟處理複雜查詢
-- 第一步:建立包含初步篩選結果的臨時表
CREATE TEMPORARY TABLE temp_active_customers AS
SELECT
customer_id,
customer_name,
email,
registration_date
FROM customers
WHERE is_active = TRUE
AND registration_date >= DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR);
-- 第二步:基於臨時表進行進一步分析
SELECT
c.customer_id,
c.customer_name,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM temp_active_customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING total_spent > 1000
ORDER BY total_spent DESC;
-- 場景 2:在迴圈或程序中重複使用中間結果
-- 建立一次,多次查詢
CREATE TEMPORARY TABLE temp_product_stats AS
SELECT
category_id,
COUNT(*) AS product_count,
AVG(price) AS avg_price,
MAX(price) AS max_price,
MIN(price) AS min_price
FROM products
GROUP BY category_id;
-- 查詢 1:找出產品數量最多的類別
SELECT * FROM temp_product_stats ORDER BY product_count DESC LIMIT 5;
-- 查詢 2:找出平均價格最高的類別
SELECT * FROM temp_product_stats ORDER BY avg_price DESC LIMIT 5;
-- 查詢 3:找出價格範圍最大的類別
SELECT
category_id,
(max_price - min_price) AS price_range
FROM temp_product_stats
ORDER BY price_range DESC LIMIT 5;
臨時表與 CTE 的比較
臨時表和 CTE(Common Table Expression)都可以用來儲存中間結果,但它們有不同的特性:
-- 使用 CTE
WITH active_customers AS (
SELECT customer_id, customer_name
FROM customers
WHERE is_active = TRUE
),
customer_orders AS (
SELECT
ac.customer_id,
ac.customer_name,
COUNT(o.order_id) AS order_count
FROM active_customers ac
LEFT JOIN orders o ON ac.customer_id = o.customer_id
GROUP BY ac.customer_id, ac.customer_name
)
SELECT * FROM customer_orders WHERE order_count > 5;
-- 使用臨時表達成相同效果
CREATE TEMPORARY TABLE temp_active_customers AS
SELECT customer_id, customer_name
FROM customers
WHERE is_active = TRUE;
CREATE TEMPORARY TABLE temp_customer_orders AS
SELECT
ac.customer_id,
ac.customer_name,
COUNT(o.order_id) AS order_count
FROM temp_active_customers ac
LEFT JOIN orders o ON ac.customer_id = o.customer_id
GROUP BY ac.customer_id, ac.customer_name;
SELECT * FROM temp_customer_orders WHERE order_count > 5;
臨時表的優點是可以建立索引、可以在多個查詢中重複使用、可以更新資料。缺點是需要額外的磁碟空間、需要明確的建立和清理步驟。CTE 的優點是語法簡潔、適合一次性使用的中間結果。缺點是無法建立索引、無法跨查詢重複使用。
查詢效能最佳化策略
撰寫功能正確的查詢只是第一步,在實際應用中還需要考慮查詢效能。以下是一些實用的最佳化策略。
選擇適當的索引
索引是提升查詢效能最有效的手段。應該為經常用於 WHERE、JOIN 和 ORDER BY 的欄位建立索引:
-- 為常用的查詢條件建立索引
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_date ON orders(order_date);
CREATE INDEX idx_products_category ON products(category_id);
-- 複合索引:適用於經常一起使用的欄位
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
避免不必要的全表掃描
某些查詢寫法會導致資料庫無法使用索引,應該盡量避免:
-- 不佳:在索引欄位上使用函數
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
-- 較佳:使用範圍查詢
SELECT * FROM orders
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';
-- 不佳:使用 LIKE '%keyword'
SELECT * FROM products WHERE product_name LIKE '%chair%';
-- 較佳:如果可能,使用前綴匹配
SELECT * FROM products WHERE product_name LIKE 'office chair%';
限制結果集大小
即使在開發階段,也應該養成限制結果集的習慣:
-- 開發時使用 LIMIT 避免取回過多資料
SELECT * FROM large_table LIMIT 100;
-- 只選取需要的欄位,而非 SELECT *
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 123;
使用 EXPLAIN 分析查詢
大多數資料庫都提供 EXPLAIN 命令來分析查詢的執行計劃:
-- 分析查詢的執行計劃
EXPLAIN SELECT
c.customer_name,
COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.registration_date >= '2024-01-01'
GROUP BY c.customer_id, c.customer_name
HAVING order_count > 5;
透過分析執行計劃,可以了解查詢是否使用了索引、預估的掃描行數、JOIN 的執行方式等資訊,從而針對性地進行最佳化。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
rectangle "查詢最佳化流程" as title
rectangle "步驟 1" as step1 {
rectangle "撰寫功能正確的查詢"
}
rectangle "步驟 2" as step2 {
rectangle "使用 EXPLAIN 分析執行計劃"
}
rectangle "步驟 3" as step3 {
rectangle "識別效能瓶頸"
}
rectangle "步驟 4" as step4 {
rectangle "建立適當的索引"
}
rectangle "步驟 5" as step5 {
rectangle "改寫查詢語句"
}
rectangle "步驟 6" as step6 {
rectangle "驗證效能改善"
}
step1 -[hidden]down-> step2
step2 -[hidden]down-> step3
step3 -[hidden]down-> step4
step4 -[hidden]down-> step5
step5 -[hidden]down-> step6
@enduml
綜合應用案例
讓我們透過一個完整的案例,綜合運用本文介紹的各種技術。假設我們需要為一個電商平台建立一個銷售報表系統。
-- 案例:電商平台銷售分析報表
-- 步驟 1:建立臨時表儲存活躍客戶的基本資訊
CREATE TEMPORARY TABLE temp_active_customers AS
SELECT
c.customer_id,
c.customer_name,
c.email,
c.membership_level,
c.registration_date
FROM customers c
WHERE c.is_active = TRUE
AND c.registration_date >= DATE_SUB(CURRENT_DATE, INTERVAL 2 YEAR);
-- 步驟 2:計算每個客戶的訂單統計
CREATE TEMPORARY TABLE temp_customer_stats AS
SELECT
ac.customer_id,
ac.customer_name,
ac.membership_level,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS total_spent,
AVG(o.total_amount) AS avg_order_value,
MAX(o.order_date) AS last_order_date
FROM temp_active_customers ac
LEFT JOIN orders o ON ac.customer_id = o.customer_id
WHERE o.status = 'Completed'
GROUP BY ac.customer_id, ac.customer_name, ac.membership_level;
-- 步驟 3:產生最終報表
-- 結合 UNION 顯示不同會員等級的統計
SELECT
'Premium' AS membership_level,
COUNT(*) AS customer_count,
SUM(total_spent) AS total_revenue,
AVG(order_count) AS avg_orders_per_customer,
AVG(avg_order_value) AS avg_order_value
FROM temp_customer_stats
WHERE membership_level = 'Premium'
UNION ALL
SELECT
'Standard' AS membership_level,
COUNT(*) AS customer_count,
SUM(total_spent) AS total_revenue,
AVG(order_count) AS avg_orders_per_customer,
AVG(avg_order_value) AS avg_order_value
FROM temp_customer_stats
WHERE membership_level = 'Standard'
UNION ALL
SELECT
'Basic' AS membership_level,
COUNT(*) AS customer_count,
SUM(total_spent) AS total_revenue,
AVG(order_count) AS avg_orders_per_customer,
AVG(avg_order_value) AS avg_order_value
FROM temp_customer_stats
WHERE membership_level = 'Basic'
ORDER BY
CASE membership_level
WHEN 'Premium' THEN 1
WHEN 'Standard' THEN 2
WHEN 'Basic' THEN 3
END;
-- 步驟 4:取得消費金額前 20 名的客戶明細
SELECT
cs.customer_name,
cs.membership_level,
cs.order_count,
cs.total_spent,
cs.avg_order_value,
cs.last_order_date,
DATEDIFF(CURRENT_DATE, cs.last_order_date) AS days_since_last_order
FROM temp_customer_stats cs
WHERE cs.total_spent > 0
ORDER BY cs.total_spent DESC
LIMIT 20;
這個案例展示了如何結合臨時表、JOIN、UNION 和 LIMIT 來建立一個完整的報表查詢。透過分步驟處理,我們可以將複雜的邏輯拆解成易於理解和維護的部分。
掌握 SQL 查詢技巧是資料庫開發的基礎能力。LIMIT 和 OFFSET 讓我們能夠控制結果集的大小,實作分頁功能。UNION 和 UNION ALL 讓我們能夠合併多個查詢的結果,整合不同來源的資料。多表 JOIN 讓我們能夠從正規化的資料庫中提取完整的資訊。臨時表讓我們能夠儲存中間結果,簡化複雜的查詢邏輯。
在實際應用中,選擇適當的技術並考慮效能因素,是撰寫高品質 SQL 查詢的關鍵。透過不斷的實踐和經驗累積,我們可以逐漸培養出在各種情境下選擇最佳方案的能力,為應用程式提供高效且可靠的資料支援。