SaaS/訂閱業務常見需求:根據開始時間、計費間隔 (月/季/半年/年)、到期時間,精確計算每個自然月是否需要出帳。本文拆解時間數學邏輯,給出基於 UNIX 時間戳的高效 SQL 實現,避免日期函數陷阱。
| 字段 | 類型 | 含義 | 示例 |
|---|---|---|---|
| ktime | INT (UNIX TS) | 服務生效/首次付費時間 | 1228406400 (2008-12-05) |
| dtime | INT (UNIX TS) | 服務到期時間 | 1259856000 (2009-12-04) |
| jmonth | TINYINT | 計費間隔月數 | 1/3/6/12 |
PERIOD_DIFF(query_ym, start_ym) = 兩月份之差 (月數)。diff % jmonth == 0 且在有效區間內 → 需付費。SET @query_ym = 200902; -- 查詢月 YYYYMM
SET @month_start_ts = UNIX_TIMESTAMP('2009-02-01 00:00:00');
SET @month_end_ts = UNIX_TIMESTAMP('2009-03-01 00:00:00');
SET @advance_sec = 5 * 86400; -- 提前 5 天出帳
= @month_start_ts AND ktime < @month_end_ts) OR -- 情況 2: 首次付費在之前,且到期時間覆蓋本月(含提前量),且周期對齊 (ktime < @month_start_ts AND dtime - @advance_sec > @month_end_ts AND PERIOD_DIFF(@query_ym, DATE_FORMAT(FROM_UNIXTIME(ktime - @advance_sec), '%Y%m') ) % jmonth = 0) ) ORDER BY ktime DESC;
| 條件分支 | 業務含義 | 示例命中 |
|---|---|---|
| ktime 在本月 | 首付月/續費首月 | 2008-12-05 開通,查 2008-12 → 命中 |
| ktime < 本月首 && dtime - 提前量 > 本月末 | 服務覆蓋整個月 (含緩衝) | 2008-12 開通,查 2009-02,到期 2009-12 → 命中 |
| PERIOD_DIFF % jmonth = 0 | 距離首付月為間隔整數倍 | jmonth=3,查 2009-02 (差 2 月) → 不命中;查 2009-03 (差 3 月) → 命中 |
FROM_UNIXTIME(ts) → DATETIMEDATE_FORMAT(dt, '%Y%m') → 整數年月 (如 200902)PERIOD_DIFF(ym1, ym2) → ym1 - ym2 (月數),支持跨年。ALTER TABLE t_list ADD INDEX idx_ktime_dtime_jmonth (ktime, dtime, jmonth);
覆蓋 WHERE 條件列,避免回表。
-- 測試數據
INSERT INTO t_list (ktime, dtime, jmonth) VALUES
(1228406400, 1259856000, 3); -- 2008-12-05 到 2009-12-04, 季付
-- 預期結果
-- 2008-12 (首月) → 有
-- 2009-02 (差 2 月) → 無
-- 2009-03 (差 3 月) → 有
-- 2009-05 (差 5 月) → 無
-- 2009-06 (差 6 月) → 有
訂閱計費查詢核心 = 時間區間覆蓋判斷 + 周期對齊取模。用 PERIOD_DIFF + DATE_FORMAT 替代複雜日期運算,邏輯清晰、可索引、跨年安全。應用層只需注入查詢月首末時間戳與 YYYYMM 整數,SQL 保持靜態,極易維護與測試。