報表、儀表板、定時任務頻繁需要「相對時間」篩選。本文提供針對 DATETIME/TIMESTAMP/INT Unix時間戳 三種欄位類型的標準 SQL 範本,並解析核心函數 TO_DAYS、DATE_SUB、DATE_FORMAT、PERIOD_DIFF 差異與陷阱。
-- 今日
WHERE DATE(add_time) = CURDATE()
-- 或
WHERE add_time >= CURDATE() AND add_time < CURDATE() + INTERVAL 1 DAY
-- 昨日
WHERE DATE(add_time) = CURDATE() - INTERVAL 1 DAY
-- 近 7 天(含今日)
WHERE add_time >= DATE_SUB(CURDATE(), INTERVAL 6 DAY)
-- 近 30 天
WHERE add_time >= DATE_SUB(CURDATE(), INTERVAL 29 DAY)
-- 本月
WHERE DATE_FORMAT(add_time, '%Y%m') = DATE_FORMAT(CURDATE(), '%Y%m')
-- 上月
WHERE PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), DATE_FORMAT(add_time, '%Y%m')) = 1
-- 今日
WHERE DATE_FORMAT(FROM_UNIXTIME(add_time), '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d')
-- 近 7 天
WHERE FROM_UNIXTIME(add_time) >= DATE_SUB(NOW(), INTERVAL 7 DAY)
| 函數 | 作用 | 陷阱 |
|---|---|---|
TO_DAYS(date) | 日期轉天數(從 year 0) | 1582 年前不可靠;僅精確到天 |
DATE_SUB(date, INTERVAL n UNIT) | 日期減法 | 單位支援 DAY/MONTH/YEAR/HOUR/MINUTE/SECOND |
DATE_FORMAT(date, fmt) | 日期格式化 | %Y%m 適合月份比較;%Y-%m-%d 適合天比較 |
PERIOD_DIFF(p1, p2) | 月份差(YYYYMM 格式) | 僅支援 YYYYMM,不支援 YYYY-MM |
STR_TO_DATE(str, fmt) | 字串轉日期 | 格式必須嚴格匹配,否則返回 NULL |
錯誤:WHERE DATE(add_time) = CURDATE() → 全表掃描,無法用索引。
正確:WHERE add_time >= '2023-10-01 00:00:00' AND add_time < '2023-10-02 00:00:00' → 可用 add_time 索引範圍掃描。
function getDateRangeSql($field, $range) {
$now = new DateTime();
switch ($range) {
case 'today':
$start = (clone $now)->setTime(0,0,0);
$end = (clone $now)->setTime(23,59,59);
break;
case 'yesterday':
$start = (clone $now)->modify('-1 day')->setTime(0,0,0);
$end = (clone $start)->setTime(23,59,59);
break;
case '7days':
$start = (clone $now)->modify('-6 days')->setTime(0,0,0);
$end = (clone $now)->setTime(23,59,59);
break;
case '30days':
$start = (clone $now)->modify('-29 days')->setTime(0,0,0);
$end = (clone $now)->setTime(23,59,59);
break;
case 'this_month':
$start = (clone $now)->modify('first day of this month')->setTime(0,0,0);
$end = (clone $now)->modify('last day of this month')->setTime(23,59,59);
break;
case 'last_month':
$start = (clone $now)->modify('first day of last month')->setTime(0,0,0);
$end = (clone $now)->modify('last day of last month')->setTime(23,59,59);
break;
}
return "`$field` >= '{$start->format('Y-m-d H:i:s')}' AND `$field` <= '{$end->format('Y-m-d H:i:s')}'";
}
BETWEEN 含邊界,DATETIME 精度到秒,易漏掉 23:59:59.xxx → 改用 >= start AND < end+1day。CURDATE() 返回日期,NOW() 返回日期時間,**別混用**導致隱式轉換。WHERE 條件必須包含分區鍵,否則全分區掃描。相對時間查詢看似簡單,**欄位類型、索引利用、時區、邊界** 四個坑踩一個就慢或錯。將上述範本封裝為工具函數/ORM Scope,業務代碼直接呼叫,才能保證一致性與效能。