模擬實現SQL Server中的datepart(week,date)的功能


本文目錄列表:
 
1、為什么要模擬實現datepart(week,date)的功能
 
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,下同),請繼續往下看。
 
 
2 、具體實現思路
 
由於知道了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代碼實現。
 
3、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

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM