DATEPART 與 DATENAME 是 SQL Server 提取日期組件的雙子星函數:前者返回整數,適合計算、分組、排序;後者返回字串,適合顯示、報表標籤。本文將從語法、參數、返回類型、week/weekday 特殊行為、時區陷阱,到動態交叉表 (按星期統計城市數據) 實戰全維度解析,助你精準選型並避開常見坑點。
| 項目 | DATEPART | DATENAME |
|---|---|---|
| 語法 | DATEPART ( datepart , date ) | DATENAME ( datepart , date ) |
| 返回類型 | int | nvarchar |
| 適用場景 | 計算、分組、排序、WHERE 篩選 | 報表顯示、標籤生成、動態列名 |
| week (wk/ww) 行為 | 受 SET DATEFIRST 影響,1月1日所在週 = 1 | 同 DATEPART |
| weekday (dw) 行為 | 返回數字:1=週日...7=週六 (受 DATEFIRST 影響) | 返回名稱:'星期日'...'星期六' (受語言環境影響) |
| datepart | 縮寫 | 說明 |
|---|---|---|
| Year | yy, yyyy | 年份 |
| Quarter | qq, q | 季度 (1-4) |
| Month | mm, m | 月份 (1-12) |
| DayOfYear | dy, y | 年內第幾天 (1-366) |
| Day | dd, d | 月內第幾天 (1-31) |
| Week | wk, ww | 年內第幾週 (受 DATEFIRST) |
| Weekday | dw | 星期幾 (受 DATEFIRST) |
| Hour | hh | 小時 (0-23) |
| Minute | mi, n | 分鐘 (0-59) |
| Second | ss, s | 秒 (0-59) |
| Millisecond | ms | 毫秒 (0-999) |
datetime、smalldatetime、datetime2、date、datetimeoffset 類型列/變量/字面量。'2023-01-01'。smalldatetime 精度僅到分鐘,秒/毫秒部分始終為 0。two digit year cutoff (默認 2049),49→2049, 50→1950。強制使用四位數年份。源數據:
| ID | City | Date |
|---|---|---|
| 1 | a | 2005-01-24 (週一) |
| 2 | b | 2005-01-23 (週日) |
| 3 | b | 2005-01-23 (週日) |
| 4 | a | 2005-01-21 (週五) |
| 5 | d | 2005-01-20 (週四) |
| 6 | c | 2005-01-19 (週三) |
| 7 | c | 2005-01-19 (週三) |
| 8 | a | 2005-01-18 (週二) |
目標報表:
| City\Day | Mon | Tue | Wed | Thu | Fri | Sat | Sun | Total |
|---|---|---|---|---|---|---|---|---|
| a | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 3 |
| b | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 2 |
| c | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 2 |
| d | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
| Total | 1 | 1 | 2 | 1 | 1 | 0 | 2 | 8 |
-- 建表
CREATE TABLE #t (id int, city varchar(2), dt datetime);
INSERT INTO #t VALUES
(1,'a','20050124'),(2,'b','20050123'),(3,'b','20050123'),
(4,'a','20050121'),(5,'d','20050120'),(6,'c','20050119'),
(7,'c','20050119'),(8,'a','20050118');
-- 核心查詢 (假設 DATEFIRST=7, 週日=1)
SELECT
city AS [City\Day],
SUM(CASE WHEN DATEPART(dw, dt) = 2 THEN 1 ELSE 0 END) AS Mon, -- 週一=2
SUM(CASE WHEN DATEPART(dw, dt) = 3 THEN 1 ELSE 0 END) AS Tue,
SUM(CASE WHEN DATEPART(dw, dt) = 4 THEN 1 ELSE 0 END) AS Wed,
SUM(CASE WHEN DATEPART(dw, dt) = 5 THEN 1 ELSE 0 END) AS Thu,
SUM(CASE WHEN DATEPART(dw, dt) = 6 THEN 1 ELSE 0 END) AS Fri,
SUM(CASE WHEN DATEPART(dw, dt) = 7 THEN 1 ELSE 0 END) AS Sat,
SUM(CASE WHEN DATEPART(dw, dt) = 1 THEN 1 ELSE 0 END) AS Sun,
COUNT(*) AS Total
FROM #t
GROUP BY city
UNION ALL
SELECT
'Total',
SUM(CASE WHEN DATEPART(dw, dt) = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 4 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 5 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 6 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 7 THEN 1 ELSE 0 END),
SUM(CASE WHEN DATEPART(dw, dt) = 1 THEN 1 ELSE 0 END),
COUNT(*)
FROM #t;
SELECT
city AS [City\Day],
SUM(CASE WHEN DATENAME(dw, dt) = N'星期一' THEN 1 ELSE 0 END) AS Mon,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期二' THEN 1 ELSE 0 END) AS Tue,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期三' THEN 1 ELSE 0 END) AS Wed,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期四' THEN 1 ELSE 0 END) AS Thu,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期五' THEN 1 ELSE 0 END) AS Fri,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期六' THEN 1 ELSE 0 END) AS Sat,
SUM(CASE WHEN DATENAME(dw, dt) = N'星期日' THEN 1 ELSE 0 END) AS Sun,
COUNT(*) AS Total
FROM #t
GROUP BY city
UNION ALL
SELECT 'Total', ... ; -- 同理
關鍵警示: DATENAME 返回值取決於 SET LANGUAGE / 登錄語言。中文環境返回 N'星期一',英文環境返回 'Monday'。跨環境部署必須用 DATEPART 或顯式 SET LANGUAGE。
SELECT City\Day, ISNULL([1],0) AS Sun, ISNULL([2],0) AS Mon, ISNULL([3],0) AS Tue,
ISNULL([4],0) AS Wed, ISNULL([5],0) AS Thu, ISNULL([6],0) AS Fri, ISNULL([7],0) AS Sat,
([1]+[2]+[3]+[4]+[5]+[6]+[7]) AS Total
FROM (
SELECT city, DATEPART(dw, dt) AS dw
FROM #t
) src
PIVOT (COUNT(dw) FOR dw IN ([1],[2],[3],[4],[5],[6],[7])) pvt;
| 需求 | 推薦函數 | 理由 |
|---|---|---|
| 分組、排序、WHERE 篩選、計算 | DATEPART | 返回 int,索引友好、運算快、跨語言穩定 |
| 報表列名、標籤顯示、用戶可讀輸出 | DATENAME | 返回本地化字串,直接可用 |
| 動態交叉表/透視表 | DATEPART + 條件聚合 / PIVOT | 性能最優,邏輯清晰 |
| 週/星期相關邏輯 | DATEPART(dw, ...) + 明確 SET DATEFIRST | 消除歧義,建議在會話/存儲過程開頭 SET DATEFIRST 7 (週日=1) |
掌握 DATEPART (整數計算) 與 DATENAME (字串顯示) 的分工本質,並始終顯式設置 SET DATEFIRST 7 統一週起始日,是編寫健壯、可移植、高性能日期邏輯的基石。現代 T-SQL 開發請優先考慮 PIVOT 替代手工條件聚合,讓優化器為你做更好的執行計劃選擇。