SQL Server 2012 引入 LEAD、LAG 讓我們能在單一掃描中比較「當前列與前/後 N 列」的值,完美解決「環比增長」、「連續登入天數」、「狀態變更偵測」等需求,效能遠優於 Self-Join 或游標。
LEAD(scalar_expression [, offset [, default]]) OVER ([PARTITION BY ...] ORDER BY ...)
LAG (scalar_expression [, offset [, default]]) OVER ([PARTITION BY ...] ORDER BY ...)
NULL,當位移超出分區邊界時回傳此值。WITH test AS (
SELECT NULL AS score UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL
SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50
)
SELECT
ROW_NUMBER() OVER (ORDER BY score) AS rownum,
score,
LEAD(score) OVER (ORDER BY score) AS next_1_def,
LEAD(score, 1) OVER (ORDER BY score) AS next_1_exp,
LEAD(score, 1, 0) OVER (ORDER BY score) AS next_1_def0,
LEAD(score, 2) OVER (ORDER BY score) AS next_2,
LAG(score) OVER (ORDER BY score) AS prev_1_def,
LAG(score, 1) OVER (ORDER BY score) AS prev_1_exp,
LAG(score, 1, 0) OVER (ORDER BY score) AS prev_1_def0,
LAG(score, 2) OVER (ORDER BY score) AS prev_2
FROM test;
| rownum | score | next_1_def | next_1_exp | next_1_def0 | next_2 |
|---|---|---|---|---|---|
| 1 | NULL | 10 | 10 | 10 | 20 |
| 2 | 10 | 20 | 20 | 20 | 30 |
| 3 | 20 | 30 | 30 | 30 | 40 |
| 4 | 30 | 40 | 40 | 40 | 50 |
| 5 | 40 | 50 | 50 | 50 | NULL |
| 6 | 50 | NULL | NULL | 0 | NULL |
LEAD(score) = LEAD(score,1):取下一列值。LEAD(score,2):取下下列值(第 X+2 列)。NULL;指定 default=0 則回傳 0。| rownum | score | prev_1_def | prev_1_exp | prev_1_def0 | prev_2 |
|---|---|---|---|---|---|
| 1 | NULL | NULL | NULL | 0 | NULL |
| 2 | 10 | NULL | NULL | NULL | NULL |
| 3 | 20 | 10 | 10 | 10 | NULL |
| 4 | 30 | 20 | 20 | 20 | 10 |
| 5 | 40 | 30 | 30 | 30 | 20 |
| 6 | 50 | 40 | 40 | 40 | 30 |
NULL(或 default)。LAG(score,2):取上上列值(第 X-2 列)。SELECT month, sales,
(sales - LAG(sales) OVER (ORDER BY month)) * 1.0 / LAG(sales) OVER (ORDER BY month) AS mom_pct
FROM monthly_sales;
SELECT *,
CASE WHEN status <> LAG(status) OVER (PARTITION BY order_id ORDER BY changed_at)
THEN 1 ELSE 0 END AS status_changed
FROM order_status_log;
SELECT user_id, login_date,
CASE WHEN login_date = DATEADD(DAY, 1, LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date))
THEN 0 ELSE 1 END AS new_streak_flag
FROM user_logins;
SELECT id, COALESCE(value, LAG(value) OVER (ORDER BY id)) AS filled_value
FROM sparse_data;
O(N log N)),無 Self-Join 的 O(N²) 或游標的逐列處理。PARTITION BY 可並行處理多組資料。LEAD 看未來,LAG 看過去。善用 offset、default、PARTITION BY,即可用極簡潔的宣告式 SQL 解決所有「跨列比較」問題,告別 Self-Join 與游標。