为什么这个DateTime会出现溢出?

时间:2015-10-20 19:14:09

标签: sql-server tsql sql-server-2014 date-arithmetic

我正在尝试创建一个动态日历,我可以忽略周末并从现在起45天内跳过

Declare @StartDate Date = '12/01/2015'

;With NumberList AS
(
        Select *
        From 
        (
            Select Rank() Over(Order By S1.Id, S2.Id) Number
            From master.dbo.SysObjects S1, master.dbo.SysObjects S2
        ) XX
        Where Number < 1000
)
,FullCalendar as
(
    select Cast (dateadd(day, number,  DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
    from Numberlist
)
,CalendarWithNumbers As 
(
    Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
    From FullCalendar
    Where ((DATEPART(dw, CalendarDate) + @@DATEFIRST) % 7) NOT IN (0, 1)
)
,StartingDate as
(
    Select *
    From CalendarWithNumbers
    Where 1=1
        And CalendarDate = @StartDate
)
Select @StartDate StartDate, CalendarDate NDaysFromNow, DayNumber
From CalendarWithNumbers 
Where DayNumber = (Select DayNumber + 45 from StartingDate)

但是,我收到了一个溢出错误

StartDate  NDaysFromNow DayNumber
---------- ------------ --------------------
Msg 517, Level 16, State 1, Line 3
Adding a value to a 'datetime' column caused an overflow.

但是,我只在我的第一个CTE中选择前1000个数字,而我的开始日期是一年中的第一天。 1/1/2015 + 1000天仅上升到9/26/2017

为什么我收到溢出错误?

编辑 - 添加注释以解释代码

我在代码中添加了一条注释,以便于理解。

Declare @StartDate Date = '12/01/2015'

;With NumberList AS
(
        /* Get a list of 1000 Numbers*/
        Select *
        From 
        (
            /* 
                Get a sequence of Numbers (I get 2071*2071 = 4289041) 
                I may need to go up to 50 years in the future so I have to do a cartesian product (same as Cross Join)
                However, this is where it fails
            */

            Select Rank() Over(Order By S1.Id, S2.Id) Number
            From master.dbo.SysObjects S1, master.dbo.SysObjects S2

            /*
                comment out the Select ABOVE 
                Uncomment the Select BELOW
                It works!!
                Probably because now the sequencing only goes up to 2071
            */
            --Select Rank() Over(Order By S1.Id) Number
            --From master.dbo.SysObjects S1

        ) XX
        Where Number < 1000
)
--Select * From NumberList
--UnComment just the line above, and comment everything below to see the result just up to there

,FullCalendar as
(
    /* Take the first day of the year, and add 1 to 1000 days to it
    It starts from 2015-01-02 (bug needs to be fixed so it starts from 2015-01-01)
    It ends at 2017-09-26
    */
    select Cast (dateadd(day, number,  DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
    from Numberlist
)
--Select * From FullCalendar
--UnComment just the line above, and comment everything below to see the result just up to there

,CalendarWithNumbers As 
(
    /* 
    WHERE CLAUSE exclude the weekend days
    ROW_NUMBER now number each day sequentially and save it in the DayNumber column
    */
    Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
    From FullCalendar
    Where ((DATEPART(dw, CalendarDate) + @@DATEFIRST) % 7) NOT IN (0, 1)
)
--Select * From CalendarWithNumbers
--UnComment just the line above, and comment everything below to see the result just up to there

,StartingDate as
(
    /* Get the DayNumber (from the query above) for the starting day*/
    Select *
    From CalendarWithNumbers
    Where 1=1
        And CalendarDate = @StartDate
)
--Select * From StartingDate
--UnComment just the line above, and comment everything below to see the result just up to there

/* 
    Now calculate 45 days from the daynumber in starting date
    that gives you the value for NDays from StartDate
*/
Select 
    SD.CalendarDate,
    Sd.DayNumber,
    CWN.CalendarDate NDaysFromNow, 
    CWN.DayNumber
From CalendarWithNumbers CWN
Inner Join StartingDate SD
    On CWN.DayNumber = SD.DayNumber + 345 

2 个答案:

答案 0 :(得分:1)

这适用于,,,,只是替换了生成NumberList

的查询
Declare @StartDate Date = '12/01/2015'  --<-- I would use '20151201'

;With NumberList AS
(
        Select *
        From 
        (
            Select ROW_NUMBER() Over(Order By (SELECT NULL)) Number
            From master..spt_values S1
        ) XX
        Where Number < 1000
)
,FullCalendar as
(
    select Cast (dateadd(day, number,  DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
    from Numberlist
)
,CalendarWithNumbers As 
(
    Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
    From FullCalendar
    Where ((DATEPART(dw, CalendarDate) + @@DATEFIRST) % 7) NOT IN (0, 1)
)
,StartingDate as
(
    Select *
    From CalendarWithNumbers
    Where 1=1
        And CalendarDate = @StartDate
)
Select @StartDate StartDate, CalendarDate NDaysFromNow, DayNumber
From CalendarWithNumbers 
Where DayNumber = (Select DayNumber + 45 from StartingDate)

答案 1 :(得分:1)

我的猜测是SQL Server决定按照你想象的不同顺序做事,实际上是这样的:

;With NumberList AS
(
        Select *
        From 
        (
            Select Rank() Over(Order By S1.Id, S2.Id) Number
            From master.dbo.SysObjects S1, master.dbo.SysObjects S2
        ) XX
        Where Number < 1000
)

在此之前不会过滤到999行;

select Cast (dateadd(day, number,  DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate

即使这是你期望发生的事情。如果将其更改为此,则错误将消失:

;With NumberList AS
(
  Select top 999 row_number() Over(Order By (select null)) Number
  From master.dbo.SysObjects S1 cross join master.dbo.SysObjects S2
)

使用top通常是一个更好的选择,因为这可以让优化器更好地了解实际来自那里的行数,而不必决定何时使用Number&gt; =来过滤行1000(或数字的分布可能是什么)