DATE_ADD() 是 MySQL 中進行日期時間加減運算的核心函數,支援從微秒到年份的所有時間單位。本文提供完整的語法參考、常見業務場景範例、效能考量與替代方案,作為開發與調試的速查手冊。
DATE_ADD(date, INTERVAL expr unit)
-- 等價別名:ADDDATE(date, INTERVAL expr unit)
-- 參數說明:
-- date: 合法的日期/時間表達式 (DATE, DATETIME, TIMESTAMP, 字串)
-- expr: 數值運算式,可為負數(實現減法)
-- unit: 時間單位關鍵字 (MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR 等)
| 單位 | 加法範例 | 減法範例 | 說明 |
|---|---|---|---|
| MICROSECOND | DATE_ADD(NOW(), INTERVAL 1 MICROSECOND) | DATE_ADD(NOW(), INTERVAL -1 MICROSECOND) | 微秒級,高精度日誌分析 |
| SECOND | DATE_ADD(NOW(), INTERVAL 30 SECOND) | DATE_ADD(NOW(), INTERVAL -30 SECOND) | 秒級,Token 過期、緩存 TTL |
| MINUTE | DATE_ADD(NOW(), INTERVAL 15 MINUTE) | DATE_ADD(NOW(), INTERVAL -15 MINUTE) | 分鐘級,定時任務窗口 |
| HOUR | DATE_ADD(NOW(), INTERVAL 2 HOUR) | DATE_ADD(NOW(), INTERVAL -2 HOUR) | 小時級,跨時區轉換、統計窗口 |
| DAY | DATE_ADD(CURDATE(), INTERVAL 1 DAY) | DATE_ADD(CURDATE(), INTERVAL -1 DAY) | 天級,日報、留存計算 |
| WEEK | DATE_ADD(CURDATE(), INTERVAL 1 WEEK) | DATE_ADD(CURDATE(), INTERVAL -1 WEEK) | 週級,週報、同比環比 |
| MONTH | DATE_ADD(CURDATE(), INTERVAL 1 MONTH) | DATE_ADD(CURDATE(), INTERVAL -1 MONTH) | 月級,月報、帳單週期 |
| QUARTER | DATE_ADD(CURDATE(), INTERVAL 1 QUARTER) | DATE_ADD(CURDATE(), INTERVAL -1 QUARTER) | 季度級,財報、季度規劃 |
| YEAR | DATE_ADD(CURDATE(), INTERVAL 1 YEAR) | DATE_ADD(CURDATE(), INTERVAL -1 YEAR) | 年級,周年慶、長期趨勢 |
-- 複合間隔:日 + 時間
DATE_ADD('2024-01-15 10:30:00', INTERVAL '1 12:30:45' DAY_SECOND)
-- 結果:2024-01-16 23:00:45
-- 年月複合
DATE_ADD('2024-01-15', INTERVAL '1-6' YEAR_MONTH)
-- 結果:2025-07-15
-- 等價減法函數
DATE_SUB(date, INTERVAL expr unit)
-- 範例:DATE_SUB(NOW(), INTERVAL 1 DAY) 等同於 DATE_ADD(NOW(), INTERVAL -1 DAY)
-- 近 30 天訂單
SELECT * FROM orders
WHERE created_at >= DATE_ADD(CURDATE(), INTERVAL -30 DAY);
-- 本月迄今 (MTD)
SELECT * FROM sales
WHERE sale_date >= DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)), INTERVAL 1 DAY)
AND sale_date < DATE_ADD(LAST_DAY(CURDATE()), INTERVAL 1 DAY);
-- Token 2 小時後過期
UPDATE user_tokens SET expires_at = DATE_ADD(NOW(), INTERVAL 2 HOUR) WHERE user_id = 1001;
-- 緩存 15 分鐘 TTL
INSERT INTO cache (key, value, expire_at)
VALUES ('key1', 'data', DATE_ADD(NOW(), INTERVAL 15 MINUTE));
-- 本月第一天 00:00:00
SELECT DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)), INTERVAL 1 DAY);
-- 本月最後一天 23:59:59
SELECT DATE_SUB(DATE_ADD(LAST_DAY(CURDATE()), INTERVAL 1 DAY), INTERVAL 1 SECOND);
-- 下個季度第一天
SELECT DATE_ADD(LAST_DAY(DATE_ADD(CURDATE(), INTERVAL (3 - MONTH(CURDATE()) % 3) MONTH)), INTERVAL 1 DAY);
-- 昨天同一時間
SELECT DATE_ADD(NOW(), INTERVAL -1 DAY);
-- 上週同一天
SELECT DATE_ADD(CURDATE(), INTERVAL -1 WEEK);
-- 去年今日
SELECT DATE_ADD(CURDATE(), INTERVAL -1 YEAR);
WHERE created_at >= DATE_ADD(NOW(), INTERVAL -30 DAY) 無法使用索引,因為 NOW() 非常數,優化器無法在編譯期確定範圍。
-- 應用層 (PHP/Java/Go 等) 計算好具體日期時間字串
-- SQL 僅接收常數
WHERE created_at >= '2024-01-15 00:00:00' -- 可命中索引
-- 或使用預處理語句綁定參數
PREPARE stmt FROM 'SELECT * FROM orders WHERE created_at >= ?';
SET @start_date = DATE_ADD(CURDATE(), INTERVAL -30 DAY); -- 會話變數在 8.0+ 可被優化器識別
EXECUTE stmt USING @start_date;
DATE_ADD('2024-01-31', INTERVAL 1 MONTH) → 2024-02-29 (閏年) / 2024-02-28 (平年)。MySQL 自動截取到目標月最後一天。TIMESTAMP 範圍限制:1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC。跨越此範圍運算會返回 NULL。**長期業務請用 DATETIME**。NOW() 返回當前會話時區時間,UTC_TIMESTAMP() 返回 UTC。跨時區應用建議統一儲存 UTC,應用層轉換。INTERVAL 運算式支援欄位引用:DATE_ADD(created_at, INTERVAL valid_days DAY),但同樣導致索引失效。| 函數/語法 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
DATE_ADD | 通用日期加減 | 語義清晰、單位豐富 | 索引失效風險 |
+ INTERVAL (8.0+) | 簡潔寫法 | created_at + INTERVAL 1 DAY 可讀性高 | 同樣索引問題 |
TIMESTAMPADD | 需返回 TIMESTAMP | 單位為數字常數 (1=SECOND...) | 語義較晦澀 |
| 應用層計算 | 高頻查詢、大表 | **索引友好**、跨資料庫可移植 | 需應用層邏輯 |
掌握 DATE_ADD 的全單位支援與複合語法,能極大簡化時間相關業務邏輯。但**生產環境高頻查詢務必將動態計算下沉到應用層**,保持 SQL 參數常數化,才能充分發揮索引效能。將本指南作為團隊時間運算規範附件,統一開發標準。