業務常需「最近 N 天」、「本月迄今」、「上季度」等相對時間統計。本文彙整最常用、索引友好的 SQL 寫法,避免 DATE_SUB(NOW()) 導致索引失效,並說明 DATE()、TO_DAYS()、DATE_FORMAT()、YEARWEEK()、QUARTER() 等函數適用場景。
-- ❌ 反例:created_at 上用函數,索引失效 WHERE DATE(created_at) = CURDATE() WHERE DATE_SUB(NOW(), INTERVAL 7 DAY) <= DATE(created_at) -- ✅ 正例:將計算移到常數側,欄位保持原樣,可走範圍索引 WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND created_at < CURDATE() + INTERVAL 1 DAY
| 需求 | 索引友好寫法(假設欄位 created_at 為 DATETIME/TIMESTAMP) |
|---|---|
| 今日 | WHERE created_at >= CURDATE() AND created_at < CURDATE() + INTERVAL 1 DAY |
| 昨日 | WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) AND created_at < CURDATE() |
| 近 7 天(含今日) | WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND created_at < CURDATE() + INTERVAL 1 DAY |
| 近 30 天 | WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND created_at < CURDATE() + INTERVAL 1 DAY |
| 本月 | WHERE created_at >= DATE_FORMAT(CURDATE(), '%Y-%m-01') AND created_at < DATE_FORMAT(CURDATE(), '%Y-%m-01') + INTERVAL 1 MONTH |
| 上月 | WHERE created_at >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01') AND created_at < DATE_FORMAT(CURDATE(), '%Y-%m-01') |
| 本季度 | WHERE QUARTER(created_at) = QUARTER(NOW()) AND YEAR(created_at) = YEAR(NOW()) (或用日期範圍: created_at >= MAKEDATE(YEAR(NOW()),1) + INTERVAL QUARTER(NOW())-1 QUARTER ...) |
| 上季度 | WHERE QUARTER(created_at) = QUARTER(DATE_SUB(NOW(), INTERVAL 1 QUARTER)) AND YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 QUARTER)) |
| 本年 | WHERE created_at >= MAKEDATE(YEAR(NOW()),1) AND created_at < MAKEDATE(YEAR(NOW()),1) + INTERVAL 1 YEAR |
| 上年 | WHERE created_at >= MAKEDATE(YEAR(NOW())-1,1) AND created_at < MAKEDATE(YEAR(NOW()),1) |
| 本周(週一為首日) | WHERE YEARWEEK(created_at, 1) = YEARWEEK(NOW(), 1) |
| 上周 | WHERE YEARWEEK(created_at, 1) = YEARWEEK(DATE_SUB(NOW(), INTERVAL 1 WEEK), 1) |
| 最近 6 個月 | WHERE created_at BETWEEN DATE_SUB(NOW(), INTERVAL 6 MONTH) AND NOW() |
CURDATE() / CURRENT_DATE:僅日期部分,無時間,適合「日」邊界計算。NOW() / CURRENT_TIMESTAMP:含時間,適合「精確到秒」邊界。DATE_SUB(date, INTERVAL n UNIT):日期減法,支援 DAY/WEEK/MONTH/QUARTER/YEAR。DATE_FORMAT(date, '%Y-%m'):字串比較,會導致索引失效,僅用於小表或無索引欄位。TO_DAYS(date):轉整數天數,WHERE TO_DAYS(NOW()) - TO_DAYS(created_at) <= 1 可用於「昨日」,但欄位側有函數,不如範圍查詢好。YEARWEEK(date, mode):mode=1 週一為首日、mode=0 週日為首日,需與業務定義一致。QUARTER(date):返回 1~4,配合 YEAR() 判斷季度。RANGE COLUMNS(created_at) 分區,配合分區裁剪自動過濾無關分區。TokuDB、RocksDB、MyRocks)或導出至 ClickHouse/Apache Doris 做 OLAP。收藏建議:將上表存為代碼片,需要時複製微調欄位名即可。核心口訣:「常數側算好邊界,欄位側裸奔跑索引」。