本文目錄列表:1、從MySQL提供的TO_DAYS和FROM_DAYS這對函數說起2、SQL Server日期時間粒度3、SQL Server周有關時間粒度4、總結語5、參考清單列表從MySQL提供的TO_DAYS和FROM_DAYS針對函數說起 學習和使用過MySQL的博友,大都知道MySQL提供...
本文目錄列表: 1、從MySQL提供的TO_DAYS和FROM_DAYS這對函數說起
2、SQL Server日期時間粒度
3、SQL Server周有關時間粒度 4、總結語 5、參考清單列表 從MySQL提供的TO_DAYS和FROM_DAYS針對函數說起 學習和使用過MySQL的博友,大都知道MySQL提供了很多針對日期和時間的函數,提供了獲取不同時間粒度上的功能。相對而然SQL Server提供的有關日期和時間函數不太多的,但是其提供的功能也是蠻強大的。還是讓我們繼續話說MySQL的日期和時間函數,發現了TO_DAYS和FROM_DAYS這一對函數:TO_DAYS將一個日期轉換為一個從1開始的整數(註意(摘自mysql官網文檔):Given a date date, returns a day number (the number of days since year 0).),FROM_DAYS將一個從1開始的整數(更嚴格的說從366開始,因為1-365得到的結果值為NULL)轉換為一個日期(摘自mysql官網文檔):Given a day number N, returns a DATE value.)。 數字和日期很顯然讓我想起序列這個概念。那什麼是序列呢?序列就是一個連續的任意相鄰兩個間隔單位值相等的集合(很想數學上的等差數列的定義),比如自然數集合(非負整數集合)(0,1,2,3,……正無窮)、整數集合(……,-3,-2,-1,0,1,2,3,……)、有符號(負)整數集合(……,-3,-2,-1)、無符號(非負)整數集合(自然數集合)、日期區間從"0001-01-01"‘到“9999-12-31“間隔單位值相差1天的日期集合、當然也可以是時間序列相鄰間隔單位小時等等。間隔單位值也可以是2、5等等任意的一個整數值(對於數字序列來說的),也可以1天,12小時,480分鐘等等(對於日期和時間序列來說的)。 從序列的定義來分析,數字序列和日期序列都具有共同的特性:間隔單位值相等。MySQL提供的這一對將整數和日期相互轉換的函數,也是基於一個基準日期的實現。MySQL的函數TO_DAYS的基準日期是”0000-01-01“的,該函數返回值還是從整數1開始計數的。 提起MySQL提供的基準日期,SQL Server引擎也提供一個預設的基準日期,那就是”1900-01-01“,下麵我們來看看如何使用這個基準日期。 SQL Server日期時間粒度 SQL Server很顯然沒有提供向MySQL中那樣的將整數和日期相互轉換的一對函數,但是我們瞭解了日期序列的特性,再結合SQL Server提供的基準日期,我們很容易實現類似的一對功能函數。在提供SQL Server版本的整數和日期相互轉換的一對函數實現前,我們還是講解日期這個時間粒度。 日期這個時間粒度就是一個表示年月日的值。SQL Server 提供的日期和時間的數據類型包括:date(3位元組)、smalldatetime(4位元組),time(5位元組)、datetime(8位元組),datetime2(8位元組)和datetimeoffset(10位元組),其中除smalldatetime和datetime以外的其他日期和時間類型是從SQL Server 2008提供的。為了保證我們的實現方案可以在SQL Server 2005+環境運行,我們只能針對性地選擇smalldatetime和datetime這兩個日期和時間數據類型。從這兩個日期和時間數據類的日期部分範圍(不包括時間部分)來看,smalldatetime的日期範圍區間是[1900-01-01,2079-06-06],datetime的日期部分範圍區間是[0001-01-01,9999-12-31]。目前我們處在21世界的,新開發的應用或項目中使用到的歷史數據也很難是19世紀以前的。基於這因素和基準日期”1900-01-01“的考慮,我們要滿足的日期部分範圍區間是[1900-01-01,9999-12-31],這個範圍區間也是我們要確定的日期時間粒度的範圍區間。 日期時間粒度的範圍區間找到了,只需要將該區間中的每一個日期和一個整數值進行一映射對應,也就是日期和整數間的相互轉換,基於2664600(該範圍區間獲得的總天數:((9999-1900) + 1)*366),使用4位元組整數(以下簡稱int)完全滿足存儲要求的。我們也看到了smalldatetime和其對應的int都是4位元組,就存儲空間而然將欄位列的數據類型設置為smalldatetime和int任意一個都是沒有問題的,但是存儲了smalldatetime的日期部分的範圍顯然不能存儲2079-06-07以後的日期時間值的,另外SQl Server引擎內部使用了兩個2位元組的整數存儲,第一個 2 位元組存儲 1900 年 1 月 1 日後的天數。另外一個 2 位元組存儲午夜後經過的分鐘數,從其存儲原理我們知道該數據類型的值從文件頁(數據頁、索引頁等等)中提取到得到smalldatetime值的過程是要經過一系列轉換(比如分別將兩個2位元組的整數轉換為日期和時間兩部分,再將兩部分串聯等等),相比較一個int數據類型的值,從文件頁提取到得到其值就沒有向smalldate那樣複雜的轉換操作。基於smallldatetime和int在存儲範圍區間和轉換複雜度這兩個方面的比較,我們在設計表欄位列時如果遇到只存儲日期時間粒度的時間值(只包含年月日的值,即日期部分值)時,可以設計為int數據類型。 SQL Server實現的日期和整數相互轉換的功能函數對,T-SQL代碼如下:
1 IF OBJECT_ID(N'dbo.ufn_Days', 'FN') IS NOT NULL 2 BEGIN 3 DROP FUNCTION dbo.ufn_Days; 4 END 5 GO 6 7 --================================== 8 -- 功能: 獲得指定日期時間基於基準日期的總天數(一個整數值) 9 -- 說明: 如果指定的日期時間為NULL或者小於基準日期“1900-01-01”時,則其值預設基準日期 10 -- 結果值為非負整數,從0開始計數。 11 -- 作者: XXX 12 -- 創建: yyyy-MM-dd 13 -- 修改: yyyy-MM-dd XXX 修改內容描述 14 -- 調用: SET @intDays = dbo.ufn_Days('2008-01-14') 15 --================================== 16 CREATE FUNCTION dbo.ufn_Days 17 ( 18 @dtmDate DATETIME -- 指定的日期時間 19 ) RETURNS INT 20 --$Encode$-- 21 AS 22 BEGIN 23 IF @dtmDate IS NULL OR @dtmDate < '1900-01-01' 24 BEGIN 25 SET @dtmDate = '1900-01-01'; 26 END 27 28 -- datepart參數也可以為dd或d 29 RETURN DATEDIFF(DAY, '1900-01-01', @dtmDate) 30 END 31 GO 32 33 IF OBJECT_ID(N'dbo.ufn_Days2Date', 'FN') IS NOT NULL 34 BEGIN 35 DROP FUNCTION dbo.ufn_Days2Date; 36 END 37 GO 38 39 --================================== 40 -- 功能: 獲得一個整數值基於基準日期對應的日期 41 -- 說明: 如果指定的整數值為NULL或為負整數時,則其值預設為0; 42 -- 如果指定的整數值大於“9999-12-31”對應的整數值時,則其值預設設置為“9999-12-31”對應的整數值 43 -- 結果值為從基準日期開始計數的日期 44 -- 作者: XXX 45 -- 創建: yyyy-MM-dd 46 -- 修改: yyyy-MM-dd XXX 修改內容描述 47 -- 調用: SET @dtmDate = dbo.ufn_Days2Date(39459) --'2008-01-14' 48 --================================== 49 CREATE FUNCTION dbo.ufn_Days2Date 50 ( 51 @intDays INT -- 指定的整數值 52 ) RETURNS DATETIME 53 --$Encode$-- 54 AS 55 BEGIN 56 IF @intDays IS NULL OR @intDays < 0 57 BEGIN 58 SET @intDays = 0; 59 END 60 61 DECLARE @intMaxDays AS INT; 62 SET @intMaxDays = dbo.ufn_Days('9999-12-31'); 63 64 IF @intDays >= @intMaxDays 65 BEGIN 66 SET @intDays = @intMaxDays; 67 END 68 69 -- datepart參數也可以為dd或d 70 RETURN DATEADD(DAY, @intDays, '1900-01-01'); 71 END 72 GO
以上功能函數對的測試T-SQL代碼如下:
1 SELECT dbo.ufn_Days(NULL) AS 'NULL值對應的整數值', dbo.ufn_Days('1899-12-31') AS '小於1900-01-01對應的整數值', dbo.ufn_Days('1900-01-01') AS '1900-01-01對應的整數值', dbo.ufn_Days('2016-01-07') AS '2016-01-07對應的整數值', dbo.ufn_Days('9999-12-31') AS '9999-12-31對應的整數值'; 2 SELECT dbo.ufn_Days2Date(NULL) AS 'NULL值對應的日期', dbo.ufn_Days2Date(-1) AS '小於0對應的日期', dbo.ufn_Days2Date(0) AS '0對應的日期', dbo.ufn_Days2Date(42374) AS '42374對應的日期', dbo.ufn_Days2Date(2958463) AS '2958463對應的整數值', dbo.ufn_Days2Date(2958464) AS '大於2958463對應的整數值'; 3 GO
執行後的查詢結果如下圖:

1 IF OBJECT_ID(N'dbo.ufn_WeekOfYear', 'FN') IS NOT NULL 2 BEGIN 3 DROP FUNCTION dbo.ufn_WeekOfYear; 4 END 5 GO 6 7 --================================== 8 -- 功能: 獲取指定日期屬於當前第幾周 9 -- 說明: 一年最多1-53周 10 -- 作者: XXX 11 -- 創建: yyyy-MM-dd 12 -- 修改: yyyy-MM-dd XXX 修改內容描述 13 -- 調用: SELECT dbo.ufn_WeekOfYear('2016-01-07'); 14 --================================== 15 CREATE FUNCTION dbo.ufn_WeekOfYear 16 ( 17 @dtmDate DATETIME 18 ) RETURNS TINYINT 19 --$Encode$-- 20 AS 21 BEGIN 22 -- datepart參數也可以為wk, ww 23 RETURN CAST(DATEPART(WEEK, @dtmDate) AS TINYINT) 24 END 25 GO 26 27 IF OBJECT_ID(N'dbo.ufn_WeekdayNameOfWeek', 'FN') IS NOT NULL 28 BEGIN 29 DROP FUNCTION dbo.ufn_WeekdayNameOfWeek; 30 END 31 GO 32 33 --================================== 34 -- 功能: 獲取指定日期屬於當前周周幾的名稱 35 -- 說明: 結果值的展示形式會以來預設設置語言 36 -- 作者: XXX 37 -- 創建: yyyy-MM-dd 38 -- 修改: yyyy-MM-dd XXX 修改內容描述 39 -- 調用: SELECT dbo.ufn_WeekOfYear('2016-01-07'); 40 --================================== 41 CREATE FUNCTION dbo.ufn_WeekdayNameOfWeek 42 ( 43 @dtmDate DATETIME 44 ) RETURNS NVARCHAR(20) 45 --$Encode$-- 46 AS 47 BEGIN 48 -- datepart參數也可以為dw 49 RETURN DATENAME(WEEKDAY, @dtmDate); 50 END 51 GO 52
測試以上函數效果的T-SQL代碼如下:
SET LANGUAGE N'us_english'; SELECT @@LANGUAGE, dbo.ufn_WeekOfYear('2017-02-01'), dbo.ufn_WeekdayNameOfWeek('2017-02-01') GO SET LANGUAGE N'簡體中文'; SELECT @@LANGUAGE, dbo.ufn_WeekOfYear('2017-02-01'), dbo.ufn_WeekdayNameOfWeek('2017-02-01') GO
執行後的查詢結果如下圖:


1 IF OBJECT_ID(N'dbo.ufn_WeekdayOfWeek', 'FN') IS NOT NULL 2 BEGIN 3 DROP FUNCTION dbo.ufn_WeekdayOfWeek; 4 END 5 GO 6 7 --================================== 8 -- 功能: 獲取指定日期隸屬當前周周幾 9 -- 說明: 結果值從1到7,分別對應從周一到周日,該值與@@DATEFISRT配置函數值保持一致 10 -- 作者: XXX 11 -- 創建: yyyy-MM-dd 12 -- 修改: yyyy-MM-dd XXX 修改內容描述 13 -- 調用: SELECT dbo.ufn_WeekdayOfWeek('2017-01-07') -- 4(表示星期四) 14 --================================== 15 CREATE FUNCTION dbo.ufn_WeekdayOfWeek 16 ( 17 @dtmDate DATETIME -- 指定的日期時間 18 ) RETURNS TINYINT 19 --$Encode$-- 20 BEGIN 21 DECLARE 22 @tintDateFirst AS TINYINT, 23 @tintWeekDayIndexID AS TINYINT, 24 @tintSum AS TINYINT; 25 26 SELECT 27 @tintDateFirst = @@DATEFIRST, 28 @tintWeekDayIndexID = DATEPART(WEEKDAY, @dtmDate), 29 @tintSum = @tintDateFirst + @tintWeekDayIndexID; 30 31 RETURN (CASE WHEN @tintSum >= 9 THEN @tintSum - 8 WHEN @tintSum = 8 THEN 7 ELSE @tintSum - 1 END); 32 END 33 GO
測試其功能的T-SQL代碼如下:
1 DECLARE @tintLoopID AS TINYINT; 2 SET @tintLoopID = 1; 3 4 DECLARE @dtmDate AS DATETIME; 5 SET @dtmDate = '2016-01-07'; 6 7 SELECT @dtmDate AS 'date', DATENAME(WEEKDAY, @dtmDate) AS 'WeekdayName'; 8 WHILE @tintLoopID <= 7 9 BEGIN 10 SET DATEFIRST @tintLoopID; 11 12 SELECT @@DATEFIRST AS 'Start Day Of Week[1=星期一、2=星期二、3=星期三,……,7=星期日]', dbo.ufn_WeekdayOfWeek(@dtmDate) AS 'Nth Of Week1=星期一、2=星期二、3=星期三,……,7=星期日]' 13 14 SET @tintLoopID = @tintLoopID + 1; 15 END 16 GO 17
執行後的查詢結果下圖:

