本文目錄列表:
在
SQL Server時間粒度系列----第2節日期、周時間粒度詳解這篇博文中,就有個函數ufn_WeekOfYear----就是用了datepart(week,date)來實現獲取,不過該函數是依賴@@datefirst這個全局變量值的,SQL Server 默認這個值時7(美國的習慣,周日作為一周的第一天),而我們中國則是周一作為一周的第一天的。后來在
SQL Server時間粒度系列----第7節日歷數據表詳解這篇博文中,依然重新實現了ufn_WeekOfYear,當時沒有充分的測試,現在發現也是存在bug的。后來又看到BIWork的這篇博文----
SQL Server - 把星期一(周一)當作每個星期的開始在一年中求取周數
,這才思考許久才重新實現類似datepart(week,date)的功能函數。
SQL Server提供的datepart(week,date)這個函數是依賴@@datefirst這個全局變量值的,由於不同的區域每周的第一天有所不同的,但是@@datefirst不能再函數中重新修改值(其實通過set datefirst num來修改的),這個是很大的不方面的,為了靈活地獲取指定日期所在當前年的星期索引數值(索引數值從1開始計數,依次為1、2、……、51、52、53、54,下同),請繼續往下看。
由於知道了datepart(week,date)和@@datefirst的依賴關系以及其存在的不方便,將@@datefirst的值以一個參數的形式出現,這樣就可以動態的設定一周的第一天啦。再加上指定的日期以及一周第一天索引數值(索引數值從1開始計數,依次為1、2、……、7,分別對應周一、周二、……、周日,例如:一周第一天索引值為1,即周一是一周的第一天)這兩個參數來實現模擬datepart(week,date)函數的功能,具體思路大致分為如下步驟:
1)、獲取指定日期所在當前年的第一天。
2)、獲取指定日期所在當前周的日索引數值(索引數值從1開始計數,依次為1、2、……、7,分別對應周一、周二、……、周日)。
3)、獲取指定日期和一周第一天索引值來獲得當前年的第一周的第一天。
4)、指定日期、當前年的第一天和當前年的第一周的第一天這三個日期進行邏輯判斷如下:
4.1)、指定日期大於等於當前年的第一天且小於當前年的第一周的第一天時,當前年的星期索引數值為1。
4.2)、4.1的否定為邏輯真時,先通過當前年的星期索引數值默認值為1在加上當前年的第一天與當前的第一周的第一天的日期天差除以7的值,然后通過當前年的第一天小於當前年的第一周的第一天時,將上邊的結果值在加上1,否則上邊的結果值就是當前年的星期索引數值。
可能以上文字表述具體的思路有些不太清楚,那就請繼續看下面的T-SQL代碼實現。
根據以上具體的思路,根據T-SQL語言來進行一種實現,T-SQL代碼實現路基如下:
1 IF OBJECT_ID(N'dbo.ufn_FirstDayOfYear', 'FN') IS NOT NULL 2 BEGIN 3 DROP FUNCTION dbo.ufn_FirstDayOfYear; 4 END 5 GO 6 7 --================================== 8 -- 功能:獲取指定日期所在當前年的第一天 9 -- 說明:具體功能說明 10 -- 兼容:運行SQL Server 2005+ 11 -- 創建:2016-07-06 09:00-09:05 劍走江湖 創建實現 12 -- 修改:yyyy-MM-dd hh:mm-hh:mm XXX 修改內容描述 13 --================================== 14 CREATE FUNCTION dbo.ufn_FirstDayOfYear 15 ( 16 @dtmDate AS DATETIME --指定日期 17 ) RETURNS DATETIME 18 AS 19 BEGIN 20 RETURN DATEADD(YEAR, DATEDIFF(YEAR, 0, @dtmDate), 0); 21 END 22 GO 23 24 IF OBJECT_ID(N'dbo.ufn_FirstWeekFirstDayOfYear', 'FN') IS NOT NULL 25 BEGIN 26 DROP FUNCTION dbo.ufn_FirstWeekFirstDayOfYear; 27 END 28 GO 29 30 --================================== 31 -- 功能:獲取指定日期所在當前年的第一周的第一天 32 -- 說明:可以通過參數@tntDateFirst動態指定一周開始的第一天,類似全局變量@@DATEFIRST的值且保持一致 33 -- 兼容:運行SQL Server 2005+ 34 -- 創建:2016-07-06 09:05-09:15 劍走江湖 創建實現 35 -- 修改:yyyy-MM-dd hh:mm-hh:mm XXX 修改內容描述 36 --================================== 37 CREATE FUNCTION dbo.ufn_FirstWeekFirstDayOfYear 38 ( 39 @dtmDate AS DATETIME --指定日期 40 ,@tntDateFirst AS TINYINT = 1 --第一天日期(從1、2、3、……、7分別對應周一、周二、周三、……、周日) 41 ) RETURNS DATETIME 42 AS 43 BEGIN 44 IF (@tntDateFirst IS NULL OR @tntDateFirst = 0 OR @tntDateFirst NOT BETWEEN 1 AND 7) 45 BEGIN 46 SET @tntDateFirst = 1; 47 END 48 49 DECLARE @dtmFirstWeekFirstDayOfYear AS DATETIME; 50 SET @dtmFirstWeekFirstDayOfYear = 0; 51 52 DECLARE 53 @dtmFirstDayOfYear AS DATETIME 54 ,@dtmStartDate AS DATETIME 55 ,@dtmEndDate AS DATETIME; 56 SELECT 57 @dtmFirstDayOfYear = [dbo].[ufn_FirstDayOfYear](@dtmDate) 58 ,@dtmStartDate = @dtmFirstDayOfYear 59 ,@dtmEndDate = DATEADD(DAY, 7, @dtmStartDate); 60 61 WHILE (@dtmStartDate <= @dtmEndDate) 62 BEGIN 63 IF ([dbo].[ufn_DayOfWeek](@dtmStartDate) = @tntDateFirst) 64 BEGIN 65 SET @dtmFirstWeekFirstDayOfYear = @dtmStartDate; 66 67 BREAK; 68 END 69 70 SET @dtmStartDate = DATEADD(DAY, 1, @dtmStartDate); 71 END 72 73 RETURN @dtmFirstWeekFirstDayOfYear; 74 END 75 GO 76 77 IF OBJECT_ID(N'dbo.ufn_DayOfWeek', 'FN') IS NOT NULL 78 BEGIN 79 DROP FUNCTION dbo.ufn_DayOfWeek; 80 END 81 GO 82 83 --================================== 84 -- 功能: 獲取指定日期時間的所在當前周的日索引值(索引值從1開始計數,依次為1、2、……、7) 85 -- 說明: 運行在SQL Server 2005+。 86 -- 結果值從1到7,分別對應從周一到周日,該值與@@DATEFISRT配置函數值保持一致。 87 -- 使用(@@datefirst + datepart(weekday, @dtmDate))%7的結果值從2、3、4、5、6、0、1 88 -- 分別對應周一、周二、周三、周四、周五、周六、周日。 89 -- 兼容:運行SQL Server 2005+ 90 -- 創建:2016-01-02 hh:mm-hh:mm 劍走江湖 創建實現 91 -- 修改:yyyy-MM-dd hh:mm-hh:mm XXX 修改內容描述 92 -- 調用: SELECT dbo.ufn_DayOfWeek('2017-01-07') -- 4(表示星期四) 93 --================================== 94 CREATE FUNCTION dbo.ufn_DayOfWeek 95 ( 96 @dtmDate AS DATETIME -- 指定的日期時間 97 ) RETURNS TINYINT 98 --$Encode$-- 99 BEGIN 100 DECLARE @tntRemainder AS TINYINT; 101 SET @tntRemainder = (@@DATEFIRST + DATEPART(WEEKDAY, @dtmDate)) % 7; 102 103 RETURN (CASE WHEN @tntRemainder <= 1 THEN @tntRemainder + 6 ELSE @tntRemainder - 1 END); 104 END 105 GO 106 107 IF OBJECT_ID(N'dbo.ufn_WeekOfYear', 'FN') IS NOT NULL 108 BEGIN 109 DROP FUNCTION dbo.ufn_WeekOfYear; 110 END 111 GO 112 113 --================================== 114 -- 功能:獲取指定日期所在當前年的星期索引值(索引值從1開始計數,依次為1、2、……、51、52、53、54) 115 -- 說明:具體功能說明 116 -- 兼容:運行SQL Server 2005+ 117 -- 創建:2016-07-06 09:15-09:35 劍走江湖 創建實現 118 -- 修改:yyyy-MM-dd hh:mm-hh:mm XXX 修改內容描述 119 --================================== 120 CREATE FUNCTION dbo.ufn_WeekOfYear 121 ( 122 @dtmDate AS DATETIME --指定日期 123 ,@tntDateFirst AS TINYINT = 1 --第一天日期(從1、2、3、……、7分別對應周一、周二、周三、……、周日) 124 ) RETURNS TINYINT 125 AS 126 BEGIN 127 DECLARE @tntWeekOfYear AS TINYINT; 128 SET @tntWeekOfYear = 1; 129 130 DECLARE 131 @dtmFirstDayOfYear AS DATETIME 132 ,@dtmFirstWeekFirstDayOfYear AS DATETIME; 133 SELECT 134 @dtmFirstDayOfYear = dbo.[ufn_FirstDayOfYear](@dtmDate) 135 ,@dtmFirstWeekFirstDayOfYear = dbo.[ufn_FirstWeekFirstDayOfYear](@dtmDate, @tntDateFirst); 136 137 IF NOT(@dtmDate >= @dtmFirstDayOfYear AND @dtmDate < @dtmFirstWeekFirstDayOfYear) 138 BEGIN 139 SET @tntWeekOfYear = @tntWeekOfYear + DATEDIFF(DAY, @dtmFirstWeekFirstDayOfYear, @dtmDate) / 7; 140 141 IF @dtmFirstDayOfYear < @dtmFirstWeekFirstDayOfYear 142 BEGIN 143 SET @tntWeekOfYear = @tntWeekOfYear + 1; 144 END 145 END 146 147 RETURN @tntWeekOfYear; 148 END 149 GO
測試代碼如下:
1 DECLARE 2 @tntDateFirst AS TINYINT 3 ,@tntMaxDateFirst AS TINYINT 4 ,@dtmStartDate AS DATETIME 5 ,@dtmEndDate AS DATETIME; 6 SELECT 7 @tntDateFirst = 1 8 ,@tntMaxDateFirst = 7 9 ,@dtmStartDate = '2000-01-01' 10 ,@dtmEndDate = '2000-01-07'; 11 WHILE (@tntDateFirst <= @tntMaxDateFirst) 12 BEGIN 13 SELECT 14 [T2].[FullDate] 15 ,[T2].[DayOfWeek] 16 ,[T2].[FirstWeekFirstDayOfYear] 17 ,@tntDateFirst AS [FirstDateOfWeek] 18 ,[T2].[WeekOfYear] 19 ,COUNT(T2.[FullDate]) OVER (PARTITION BY YEAR(T2.[FullDate]), T2.[WeekOfYear]) AS DayCountOfWeek 20 ,T2.[DefaultDayOfWeek] 21 ,@@DATEFIRST AS [DefaultFirstDateOfWeek] 22 ,[T2].[DefaultWeekOfYear] 23 ,COUNT(T2.[FullDate]) OVER (PARTITION BY YEAR(T2.[FullDate]), T2.[DefaultWeekOfYear]) AS DefaultDayCountOfWeek 24 FROM ( 25 SELECT 26 T.[FullDate] 27 ,[dbo].[ufn_DayOfWeek](T.[FullDate]) AS [DayOfWeek] 28 ,[dbo].[ufn_FirstWeekFirstDayOfYear](T.[FullDate], @tntDateFirst) AS [FirstWeekFirstDayOfYear] 29 ,[dbo].[ufn_WeekOfYear](T.[FullDate], @tntDateFirst) AS [WeekOfYear] 30 ,DATEPART(WEEKDAY, T.[FullDate]) AS [DefaultDayOfWeek] 31 ,DATEPART(WEEK, T.[FullDate]) AS [DefaultWeekOfYear] 32 FROM ( 33 SELECT DATEADD(DAY, [Num], @dtmStartDate) AS FullDate 34 FROM [dbo].[ufn_GetNums](0, DATEDIFF(DAY, @dtmStartDate, @dtmEndDate)) 35 ) AS T 36 ) AS T2; 37 38 SET @tntDateFirst = @tntDateFirst + 1; 39 END 40 GO
以上測試效果如下圖:




由於我之前寫過幾個版本的ufn_WeekOfYear實現,但是通過博文發出去的有兩個版本的,直到看到BIWork的SQL Server - 把星期一(周一)當作每個星期的開始在一年中求取周數這篇博文才發現自己之前發布的兩個博文中的ufn_WeekOfYear是存在問題的,這才花費了不少時間再次梳理和思考這個功能的實現,都說變成代碼需要測試,T-SQL代碼一樣要嚴格的單元測試才行,不然真是迷惑了自己,也誤導了別人的。
5.5、
SQL Server數字輔助表的實現