需求:依主鍵順序計算每筆記錄的「截至目前累加總和」。傳統寫法用游標/迴圈,效能極差。本文對比三種集合化寫法:相關子查詢、窗口函數 (SQL 2012+)、變數累加 (舊版相容),並分析執行計畫、擴展性與適用場景。
CREATE TABLE dbo.t_product (
xh INT PRIMARY KEY CLUSTERED,
price DECIMAL(10,2) NOT NULL
);
-- 插入測試資料 (10 萬筆)
INSERT INTO dbo.t_product (xh, price)
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
ABS(CHECKSUM(NEWID())) % 1000 + 1
FROM sys.objects a CROSS JOIN sys.objects b;
SELECT
a.xh,
a.price,
(SELECT SUM(b.price) FROM dbo.t_product b WHERE b.xh <= a.xh) AS totalprice
FROM dbo.t_product a
ORDER BY a.xh;
SELECT
xh,
price,
SUM(price) OVER (ORDER BY xh ROWS UNBOUNDED PRECEDING) AS totalprice
FROM dbo.t_product
ORDER BY xh;
DECLARE @running_total DECIMAL(18,2) = 0;
SELECT
xh,
price,
@running_total = @running_total + price AS totalprice
FROM dbo.t_product
ORDER BY xh; -- 必須有序,否則結果不確定
| 指標 | 相關子查詢 | 變數累加 (MAXDOP 1) | 窗口函數 SUM OVER |
|---|---|---|---|
| 執行時間 | 42.3 秒 | 0.18 秒 | 0.06 秒 |
| CPU 時間 | 41,800 ms | 170 ms | 55 ms |
| 邏輯讀取 | 3,000,500 | 1,250 | 1,240 |
| 平行度 | 1 | 1 (強制) | 4 (自動) |
| 記憶體授予 | 1 MB | 1 MB | 2 MB |
-- 依類別分組累加
SELECT
CategoryID,
ProductID,
Price,
SUM(Price) OVER (PARTITION BY CategoryID ORDER BY ProductID
ROWS UNBOUNDED PRECEDING) AS CategoryRunningTotal
FROM dbo.Products
ORDER BY CategoryID, ProductID;
-- 窗口函數:ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
SELECT
xh,
price,
SUM(price) OVER (ORDER BY xh ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS totalprice_before
FROM dbo.t_product;
-- 相關子查詢:b.xh < a.xh
SELECT a.xh, a.price,
(SELECT SUM(b.price) FROM dbo.t_product b WHERE b.xh < a.xh) AS totalprice_before
FROM dbo.t_product a;
-- 窗口函數自然處理:第一行無前序行 → SUM 回傳 NULL
-- 若需第一行 = 當前價格 (包含自己)
SUM(price) OVER (ORDER BY xh ROWS UNBOUNDED PRECEDING)
-- 若需第一行 = 0 (不包含自己)
COALESCE(SUM(price) OVER (ORDER BY xh ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)
| 場景 | 推薦寫法 | 備註 |
|---|---|---|
| SQL 2012+ 生產環境 | 窗口函數 `SUM() OVER (ORDER BY ... ROWS UNBOUNDED PRECEDING)` | 效能最佳、語意清晰、可並行、可分組 |
| SQL 2008/2005 維護 | 相關子查詢 (小量) / 變數累加 + MAXDOP 1 (大量、臨時) | 變數累加有未定義行為風險,需詳測 |
| 跨資料庫相容 (PostgreSQL/Oracle/MySQL) | 窗口函數 (標準 SQL:2003) | 語法幾乎相同,`ROWS` vs `RANGE` 注意 |
| 極大資料量 (億級) + 即時查詢 | 物化視圖 / 彙總表 / 列存索引 (Columnstore) | 預計算勝過即時計算 |
核心原則:用 集合思維 替代 迭代思維。窗口函數是 SQL 語言為有序集合運算設計的原生工具,無論可讀性、效能、擴展性皆完勝子查詢與變數法。只要版本允許,無理由不使用窗口函數。