查詢「今天的記錄」、「本周數據」、「上個月統計」是業務高頻需求。MySQL 提供多種日期函數組合實現,但效能與正確性差異巨大。本文系統整理各時間顆粒度的標準寫法,並重點對比索引友好與非友好寫法。
| 函數 | 用途 | 示例結果 |
|---|---|---|
DATE(dt) | 提取日期部分 | DATE('2024-01-15 14:30') → '2024-01-15' |
CURDATE() | 當前日期 | '2024-01-15' |
NOW() | 當前日期時間 | '2024-01-15 14:30:00' |
TO_DAYS(dt) | 日期轉天數(從 Year 0) | 738500 |
YEARWEEK(dt, mode) | 年周數 | 202403 |
DATE_FORMAT(dt, fmt) | 格式化 | '%Y-%m' → '2024-01' |
DATE_SUB(dt, INTERVAL n UNIT) | 日期減間隔 | 7天前 |
PERIOD_DIFF(p1, p2) | 月份差 | PERIOD_DIFF(202401, 202312)=1 |
-- ✅ 索引友好:範圍查詢
SELECT * FROM t WHERE dt_col >= '2024-01-15 00:00:00' AND dt_col < '2024-01-16 00:00:00';
-- ⚠️ 函數導致索引失效(MySQL 5.6.5+ DATE() 可用索引)
SELECT * FROM t WHERE DATE(dt_col) = CURDATE();
SELECT * FROM t WHERE dt_col >= DATE_SUB(CURDATE(), INTERVAL 1 DAY)
AND dt_col < CURDATE();
SELECT * FROM t WHERE dt_col >= DATE_SUB(CURDATE(), INTERVAL 7 DAY);
SELECT * FROM t WHERE dt_col >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);
SELECT * FROM t WHERE YEARWEEK(dt_col, 3) = YEARWEEK(CURDATE(), 3);
SELECT * FROM t WHERE YEARWEEK(dt_col, 3) = YEARWEEK(CURDATE(), 3) - 1;
SELECT * FROM t WHERE dt_col >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
AND dt_col < DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH, '%Y-%m-01');
SELECT * FROM t WHERE dt_col >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01')
AND dt_col < DATE_FORMAT(CURDATE(), '%Y-%m-01');
-- 今日
SELECT * FROM t WHERE DATE(FROM_UNIXTIME(ts_col)) = CURDATE();
-- 近 7 天
SELECT * FROM t WHERE FROM_UNIXTIME(ts_col) >= DATE_SUB(NOW(), INTERVAL 7 DAY);
SELECT COUNT(*),
SUM(IF(logusertype=2, 1, 0)) AS type2_cnt,
SUM(IF(logusertype=3, 1, 0)) AS type3_cnt
FROM testlog
WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(logendtime) <= 30;
| 寫法 | 能否用索引 | 建議 |
|---|---|---|
WHERE DATE(dt) = CURDATE() | MySQL 5.6.5+ 可,舊版不可 | 改用範圍查詢最穩妥 |
WHERE YEAR(dt)=2024 AND MONTH(dt)=1 | ❌ 不能 | 改用範圍 |
WHERE DATE_FORMAT(dt, '%Y%m') = '202401' | ❌ 不能 | 改用範圍 |
WHERE dt >= '2024-01-01' AND dt < '2024-02-01' | ✅ 能 | 推薦寫法 |
WHERE ts >= UNIX_TIMESTAMP('2024-01-01') AND ts < UNIX_TIMESTAMP('2024-02-01') | ✅ 能 | INT 時間戳同理 |
// PHP 範例
$todayStart = (new DateTime())->setTime(0,0,0)->format('Y-m-d H:i:s');
$todayEnd = (clone $todayStart)->modify('+1 day')->format('Y-m-d H:i:s');
$stmt = $pdo->prepare("SELECT * FROM orders WHERE created_at >= ? AND created_at < ?");
$stmt->execute([$todayStart, $todayEnd]);
優勢:SQL 固定可複用執行計劃、無函數依賴、跨時區邏輯集中在應用層。
YEARWEEK() 第二參數 mode 決定周起始日與第 1 周定義:
| Mode | 周起始 | 第 1 周定義 |
|---|---|---|
| 0 | Sunday | 第一個周日 |
| 1 | Monday | 第一個周一(ISO 8601) |
| 2 | Sunday | 第一個有 4+ 天的周 |
| 3 | Monday | 第一個有 4+ 天的周(ISO 8601) |
| 4-7 | 類似 | 不同第 1 周定義 |
業務必須確認「本周」定義,統一應用層與資料庫層的 mode。
掌握索引友好的相對時間查詢寫法,是將業務邏輯高效下推至資料庫層的關鍵技能。