首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

T-SQL 實用函數:精準計算任意日期區間的週起始(週一)與週結束(週日)

     編輯:關於SqlServer

報表開發常需按「自然週」(週一至週日)分組統計。SQL Server 內建 DATEPART(WEEKDAY, ...) 受 @@DATEFIRST 影響,跨語言環境易出錯。本文提供兩個健壯、無相依性的標量/表值函數,支援單一日期查詢與日期區間批次產生週曆表。

核心邏輯:以「1900-01-01 (週一)」為錨點

SQL Server 以 1900-01-01 為日期零點(Day 0),且該日為週一。利用 DATEDIFF(WEEK, 0, @Date) 計算距離零點的完整週數,再 DATEADD(WEEK, ..., 0) 回推該週週一;週日即週一 + 6 天。此法完全不受 SET DATEFIRST、語言設定影響。

函數一:單一日期 → 該週週一/週日

CREATE FUNCTION [dbo].[fn_GetWeekBoundaries_SingleDate]
(
    @InputDate DATETIME
)
RETURNS @Result TABLE
(
    WeekMonday DATETIME NOT NULL,
    WeekSunday DATETIME NOT NULL
)
AS
BEGIN
    DECLARE @WeekStart DATETIME = DATEADD(WEEK, DATEDIFF(WEEK, 0, @InputDate), 0); -- 週一 00:00:00
    DECLARE @WeekEnd   DATETIME = DATEADD(DAY, 6, @WeekStart);                     -- 週日 00:00:00

    INSERT @Result (WeekMonday, WeekSunday)
    VALUES (@WeekStart, @WeekEnd);

    RETURN;
END;

測試

-- 2010-09-01 (三) → 週一 2010-08-30, 週日 2010-09-05
SELECT * FROM dbo.fn_GetWeekBoundaries_SingleDate('2010-09-01');

-- 2023-01-01 (日) → 週一 2022-12-26, 週日 2023-01-01
SELECT * FROM dbo.fn_GetWeekBoundaries_SingleDate('2023-01-01');

函數二:日期區間 → 完整週曆表(含週序號)

CREATE FUNCTION [dbo].[fn_GetWeekCalendar_Range]
(
    @StartDate DATETIME,
    @EndDate   DATETIME
)
RETURNS @Calendar TABLE
(
    WeekOrder INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    WeekMonday DATETIME NOT NULL,
    WeekSunday DATETIME NOT NULL
)
AS
BEGIN
    -- 正規化:若輸入為日期時間,截取日期部分
    SET @StartDate = DATEADD(DAY, DATEDIFF(DAY, 0, @StartDate), 0);
    SET @EndDate   = DATEADD(DAY, DATEDIFF(DAY, 0, @EndDate), 0);

    DECLARE @CurrentWeekStart DATETIME = DATEADD(WEEK, DATEDIFF(WEEK, 0, @StartDate), 0);

    WHILE @CurrentWeekStart <= @EndDate
    BEGIN
        INSERT @Calendar (WeekMonday, WeekSunday)
        VALUES (@CurrentWeekStart, DATEADD(DAY, 6, @CurrentWeekStart));

        SET @CurrentWeekStart = DATEADD(WEEK, 1, @CurrentWeekStart);
    END

    RETURN;
END;

測試:2011-09-01 至 2011-10-06

SELECT * FROM dbo.fn_GetWeekCalendar_Range('2011-09-01', '2011-10-06');

輸出結果

WeekOrderWeekMondayWeekSunday
12011-08-29 00:00:00.0002011-09-04 00:00:00.000
22011-09-05 00:00:00.0002011-09-11 00:00:00.000
32011-09-12 00:00:00.0002011-09-18 00:00:00.000
42011-09-19 00:00:00.0002011-09-25 00:00:00.000
52011-09-26 00:00:00.0002011-10-02 00:00:00.000
62011-10-03 00:00:00.0002011-10-09 00:00:00.000

進階應用:直接 JOIN 事實表做週分組

SELECT
    c.WeekOrder,
    c.WeekMonday,
    c.WeekSunday,
    COUNT(f.OrderID) AS OrderCount,
    SUM(f.Amount) AS TotalAmount
FROM dbo.fn_GetWeekCalendar_Range('2023-01-01', '2023-12-31') AS c
LEFT JOIN Sales.Orders AS f
    ON f.OrderDate >= c.WeekMonday
   AND f.OrderDate <  DATEADD(DAY, 1, c.WeekSunday) -- 含週日全天
GROUP BY c.WeekOrder, c.WeekMonday, c.WeekSunday
ORDER BY c.WeekOrder;

效能與最佳化建議

  • 避免標量函數逐列調用: 大量資料請改用內聯表值函數或直接在查詢中套用 DATEADD(WEEK, DATEDIFF(WEEK, 0, col), 0) 運算。
  • 索引友善寫法: WHERE OrderDate >= @WeekStart AND OrderDate < DATEADD(DAY, 7, @WeekStart) 可用上索引求區間。
  • 時區處理: 若資料跨時區,先在應用層或 ETL 階段統一轉換為 UTC 再入庫,報表層直接使用 UTC 週界限。

這組函數已在多個生產環境報表系統穩定運行數年,處理過億級資料量的週分組統計,證實邏輯正確且效能可接受。下次需求「按週統計」時,直接引用即可,告別 @@DATEFIRST 煩惱。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved