先感嘆一句!好長時間沒有更新博客了!偶爾看到一句話,覺得被電擊了 - 庸人敗於懶,能人敗於傲!

-- The default first date in a week is Sunday, the value is 7
SELECT @@DATEFIRST
-- Default DATEFIRST is Sunday
SELECT DATENAME(WEEK,'2013-12-31') AS WeekName -- 53
SELECT DATENAME(WEEK,'2014-01-01') AS WeekName -- 1
SELECT DATENAME(WEEK,'2014-01-05') AS WeekName -- 2
-- Change the DATEFIRST to 1, Monday will be the first day of week.
SET DATEFIRST 1
SELECT @@DATEFIRST -- 1
-- After change the DATEFIRST to Monday
SELECT DATENAME(WEEK,'2013-12-31') AS WeekName -- 53
SELECT DATENAME(WEEK,'2014-01-01') AS WeekName -- 1
SELECT DATENAME(WEEK,'2014-01-05') AS WeekName -- 1
要注意的是 SET DATEFIRST 只在當前執行中有效,也就說比如新開一個查詢頁面繼續查詢 SELECT @@DATEFIRST 則還是顯示默認值 7。
在創建時間維度的代碼中添加 SET DATEFIRST 1,表示每周以周一開始。
USE BIWORK_SSIS
GO
SET NOCOUNT ON
-- 設置每周的起始天為周一
SET DATEFIRST 1
IF OBJECT_ID('DimDateStartWithMonday','U') IS NOT NULL
DROP TABLE DimDateStartWithMonday
GO
CREATE TABLE DimDateStartWithMonday
(
DateKey INT PRIMARY KEY,
FullDate DATE NOT NULL,
[DateName] NVARCHAR(20),
DayNumberOfWeek TINYINT NOT NULL,
DayNameOfWeek NVARCHAR(10) NOT NULL,
DayNumberOfMonth TINYINT NOT NULL,
DayNumberOfYear SMALLINT NOT NULL,
WeekNumberOfYear TINYINT NOT NULL,
EnglishMonthName NVARCHAR(10) NOT NULL,
MonthNumberOfYear TINYINT NOT NULL,
CalendarQuarter TINYINT NOT NULL,
CalendarSemester TINYINT NOT NULL,
CalendarYear SMALLINT NOT NULL
)
DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
SELECT @StartDate = '2001-01-01',
@EndDate = '2035-12-31'
WHILE (@StartDate <= @EndDate)
BEGIN
INSERT INTO DimDateStartWithMonday
(
DateKey,
FullDate,
[DateName],
DayNumberOfWeek,
DayNameOfWeek,
DayNumberOfMonth,
DayNumberOfYear,
WeekNumberOfYear,
EnglishMonthName,
MonthNumberOfYear,
CalendarQuarter,
CalendarSemester,
CalendarYear
)
SELECT CAST(CONVERT(VARCHAR(8),@StartDate,112) AS INT) AS DateKey,
CONVERT(VARCHAR(10), @StartDate,20) AS FullDate,
CONVERT(VARCHAR(20), @StartDate,106) AS [DateName],
DATEPART(DW,@StartDate) AS DayNumberOfWeek,
DATENAME(DW,@StartDate) AS DayNameOfWeek,
DATENAME(DD,@StartDate) AS [DayOfMonth],
DATENAME(DY,@StartDate) AS [DayOfYear],
DATEPART(WW,@StartDate) AS WeekNumberOfYear,
DATENAME(MM,@StartDate) AS EnglishMonthName,
DATEPART(MM,@StartDate) AS MonthNumberOfYear,
DATEPART(QQ,@StartDate) AS CalendarQuarter,
CASE WHEN DATEPART(MM,@StartDate) BETWEEN 1 AND 6
THEN 1
ELSE 2
END AS CalendarSemester,
DATEPART(YY,@StartDate) AS CalendarYear
SET @StartDate = @StartDate + 1
END
GO
最后是函數,這個函數麻煩的地方就是要考慮周日的情況。默認情況下,周日是每個星期的第一天,但是這里改成了周一是每周的第一天,邏輯上就會復雜很多。
比如,2012-01-01 是周日,2012-01-02 是周一。按默認情況,這兩天的 Week Number 都是 1,但是這里需要把 2012-01-02 的 Week Number 變成 2。
比如,2011-01-01 是周六,2012-01-02 是周日。按默認情況,周六的 Week Number 是 1, 周日的是 2。但是這里需要把周六和周日的都變成 1, 周一的變成 2。
除此之外,還要考慮之后的每一個周日與周一的交替情況。
USE BIWORK_SSIS
GO
IF OBJECT_ID('ETLWORK_GETWEEKNUMBER','FN') IS NOT NULL
DROP FUNCTION ETLWORK_GETWEEKNUMBER
GO
CREATE FUNCTION ETLWORK_GETWEEKNUMBER(@DATE DATETIME)
RETURNS INTEGER
AS
BEGIN
DECLARE @FIRST_DATE_OF_YEAR DATETIME = DATEADD(YYYY,DATEDIFF(YYYY,0,@DATE),0)
-- DECLARE @MONDAY_OF_WEEK DATETIME = DATEADD(WK,DATEDIFF(WK,0,@DATE),0)
-- DECLARE @PREVIOUS_DATE DATETIME = DATEADD(DAY,-1,@DATE)
DECLARE @WEEK_NUMBER INTEGER
-- 如果當前時間是當前年的第一天
IF @DATE = @FIRST_DATE_OF_YEAR
SET @WEEK_NUMBER = 1
-- 星期天是年第一天的情況
ELSE IF (DATEPART(WEEKDAY,@DATE) = 1 AND DATEDIFF(DAYOFYEAR,@FIRST_DATE_OF_YEAR,@DATE)/7 + 1 = DATEPART(WEEK,@DATE))
SET @WEEK_NUMBER = DATEPART(WEEK,@DATE)
-- 星期天不是年第一天的情況
ELSE IF (DATEPART(WEEKDAY,@DATE) = 1 AND DATEDIFF(DAYOFYEAR,@FIRST_DATE_OF_YEAR,@DATE)/7 + 1 <> DATEPART(WEEK,@DATE))
SET @WEEK_NUMBER = DATEPART(WEEK,@DATE) - 1
-- 如果當前天的上一個周日小於年第一天
ELSE IF DATEADD(DAY,-1,DATEADD(WK,DATEDIFF(WK,0,@DATE),0)) < @FIRST_DATE_OF_YEAR
SET @WEEK_NUMBER = 1
-- 當前天前面的一個周日正好是以周日為開始年的 7 倍的天數
ELSE IF DATEDIFF(DAYOFYEAR,@FIRST_DATE_OF_YEAR,DATEADD(DAY,-1,DATEADD(WK,DATEDIFF(WK,0,@DATE),0) ))/7 + 1 = DATEPART(WEEK,@DATE)
SET @WEEK_NUMBER = DATEPART(WEEK,@DATE) + 1
ELSE
SET @WEEK_NUMBER = DATEPART(WEEK,@DATE)
RETURN @WEEK_NUMBER
END
GO
為了方便理解,可以查看下面的查詢。
DECLARE @DATE DATETIME = '2012-01-29'
DECLARE @FIRST_DATE_OF_YEAR DATETIME = DATEADD(YYYY,DATEDIFF(YYYY,0,@DATE),0)
SELECT DATEPART(WEEK,@DATE), -- 一年中的周數,默認以周日開始
DATEADD(WK,DATEDIFF(WK,0,@DATE),0), -- 當前周的周一,默認從周日開始,但是仍然找周一
DATEADD(DAY,-1,DATEADD(WK,DATEDIFF(WK,0,@DATE),0)), -- 當前周先找周一,然后往前一天找到周日
DATEDIFF(DAYOFYEAR,@FIRST_DATE_OF_YEAR,DATEADD(DAY,-1,DATEADD(WK,DATEDIFF(WK,0,@DATE),0))), -- 當前天離年第一天的間隔
DATEDIFF(DAYOFYEAR,@FIRST_DATE_OF_YEAR,DATEADD(DAY,-1,DATEADD(WK,DATEDIFF(WK,0,@DATE),0) ))/7 + 1 -- 按天計算的周數
測試一下,查找不匹配的 Week Number - 30多年的數據結果都匹配,記得要新開一個頁面,以免之前 SET DATEFIRST 的影響。
查詢部分數據的 WeekNumber。
當然,我感覺寫的還是有點復雜,誰解決過類似問題的,期望有人能提出更簡潔的寫法。
更多 BI 文章請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server)
如果覺得這篇文章看了對您有幫助,請幫助推薦,以方便他人在 BIWORK 博客推薦欄中快速看到這些文章。