首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

SQL 精準計算年齡函數:解決閏年、生日邊界與負值邊界條件

     編輯:MySQL 資料庫相關知識

為什麼 `DATEDIFF(YEAR, ...)` 不夠用?

單純用 `DATEDIFF(YEAR, birth, today)` 只計算「年份界線跨越次數」,忽略月日是否已過,導致生日未到卻多算一歲。本文提供生產級標量函數,處理閏年 2/29、跨世紀閏年規則、負年齡修正等完整邊界條件。

生產級年齡計算函數 (T-SQL)

CREATE FUNCTION dbo.Get_FullAge
(
    @Birthday DATETIME,
    @CurrentDay DATETIME
)
RETURNS INT
AS
BEGIN
    DECLARE @Age INT;

    -- 1. 粗略年差 (跨越幾次 1/1 界線)
    SET @Age = DATEDIFF(YEAR, @Birthday, @CurrentDay);

    -- 2. 精修:若今年生日尚未到,減 1
    -- 原理:把出生日期加上粗略年差,得到「今年的生日」,若大於今天,代表還沒過生日
    IF DATEDIFF(DAY, DATEADD(YEAR, @Age, @Birthday), @CurrentDay) < 0
        SET @Age = @Age - 1;

    -- 3. 閏年特例:2/29 出生者,非閏年 3/1 視為生日
    -- 若出生 2/29,且今天是非閏年 3/1,DATEDIFF 會誤判為已過生日,需再減 1
    IF DATEPART(MONTH, @Birthday) = 2
       AND DATEPART(DAY, @Birthday) = 29
       AND DATEPART(MONTH, @CurrentDay) = 3
       AND DATEPART(DAY, @CurrentDay) = 1
       AND NOT (YEAR(@CurrentDay) % 4 = 0 AND (YEAR(@CurrentDay) % 100 != 0 OR YEAR(@CurrentDay) % 400 = 0))
        SET @Age = @Age - 1;

    -- 4. 防禦:出生日期在未來 (髒資料) 回傳 0
    IF @Age < 0
        SET @Age = 0;

    RETURN @Age;
END;

函數邏輯逐行解析

  1. 基準年差:`DATEDIFF(YEAR, ...)` 取得粗略值。
  2. 生日邊界修正:`DATEADD(YEAR, @Age, @Birthday)` 產生「今年生日」,若 > 今天 → 還沒過生日 → -1。DATEDIFF(DAY, ..., @CurrentDay) < 0 等價於「今年生日 > 今天」。
  3. 閏年 2/29 特例:
    • 出生 2/29,非閏年無 2/29,習慣視 3/1 為生日。
    • 若今天 = 非閏年 3/1,步驟 2 會誤判「今年生日 (3/1) <= 今天」→ 不減 1,實際應減 1。
    • 條件判斷:出生月日=2/29、今天月日=3/1、今年非閏年 → 再 -1。
  4. 負值防護:髒資料導致出生 > 當前,回傳 0 而非負數。

常見簡易寫法缺陷對比

寫法缺陷適用場景
DATEDIFF(YEAR, birth, GETDATE())生日未到多算 1 歲粗略統計、不嚴謹報表
FLOOR(DATEDIFF(DAY, birth, GETDATE()) / 365.25)閏年平均近似,邊界仍有誤差近似估算
本文函數無 (完整處理邊界)精準業務:保險、醫療、會員等級、法定年齡

跨資料庫移植提示

  • MySQL:`TIMESTAMPDIFF(YEAR, birth, CURDATE())` 內建正確處理生日邊界,無需自寫函數。
  • PostgreSQL:`DATE_PART('year', AGE(birth))` 或 `EXTRACT(YEAR FROM AGE(birth))` 正確。
  • Oracle:`FLOOR(MONTHS_BETWEEN(SYSDATE, birth)/12)` 需配合邊界判斷。

效能建議

函數為標量 UDF,大量資料查詢時會逐行執行,影響效能。高頻查詢建議:

  1. 改寫為 Inline Table-Valued Function (iTVF) 或直接在查詢中展開邏輯。
  2. 或將計算結果物化儲存 (如每日批次更新 `Age` 欄位)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved