SQL:比较两个日期时“无法构造数据类型日期”

时间:2019-10-04 19:42:08

标签: sql sql-server date

我在SQL Server引发错误的查询中遇到问题

  

无法构造数据类型日期,某些参数的值无效

比较两个本身有效的日期对象时。

如果我删除where子句,它会无错误地解析,但是当我尝试将它们与任何关系或相等运算符进行比较时,它会再次出错。

重现该问题的最小查询如下:

with Years as 
(
    select 
        YEAR(getdate()) + 1 Year, 
        DATEFROMPARTS(YEAR(getdate()) + 1, 1, 1) FirstOfTheYear, 
        0 YearOffset
    union all
    select 
        Year - 1,
        DATEFROMPARTS(Year - 1, 1, 1),
        YearOffset + 1
    from Years
    where YearOffset < 5
),
Months as
(
    select 1 Month
    union all
    select Month + 1
    from Months
    where Month < 12
),
Days as 
(
    select 1 Day
    union all
    select Day + 1
    from Days
    where Day < 31
), 
Dates as 
(
    select cast(DATEFROMPARTS(Year, Month, Day) as date) Date
    from Years
    cross join Months
    cross join Days
    where DAY(EOMONTH(FirstOfTheYear, Month - 1)) >= Day
)
select Dates.Date, cast ('2019-10-01' as date), CAST ('2019-10-11' as date)
from Dates
where Date = cast ('2019-10-01' as date) -- Comment this line out and the error goes away, occurs with any date construction pattern
--where Dates.[Date] >= datefromparts(2019, 10, 01) and Dates.[Date] <= DATEFROMPARTS(2019, 10, 11)
order by date

注释where子句会按预期返回结果,确认是触发此问题的比较。

此外,手动创建一些日期(查询中的第一年为2015-2019年的十月日期)并对其进行查询不会导致显示错误。

编辑:我想强调一下,该代码已经正确处理了2月和and年。 Dates CTE的输出有效,并且输出整个范围而没有错误。当我在where子句中引用引发错误的日期时,它只是

Edit2:我能够通过切换到其他日期生成模式(以递归方式每天添加一天,一天)来解决我的问题,但是我仍然很好奇是什么导致了此错误。

3 个答案:

答案 0 :(得分:1)

另外两个答案的重点是,以您自己的方式攻击问题不一定是生成日期表的最有效方法。在大多数情况下,如果受到SQL Server的约束,人们会导致某人为此使用Tally表。这样做将仍然是基于SET的操作,而不需要循环或递归。这意味着您在其中一条评论中提到的递归限制根本不适用。

Tally表是一组数字值,您可以将其用于生成或产生所需的值。在这种情况下,大约是1827天(5年+ 1天),但可能因leap年而异。 years年和2月可能是您的代码中的问题。无论如何要生成一个计数表,您都可以从10个值开始,然后交叉联接,直到获得可接受数量的组合。 3个交叉联接将为您提供10,000个值,并且ROW_NUMBER() - 1可用于生成基于0的增量。之后,您可以使用DATEADD()实际创建日期:

;WITH cteTen AS (
    SELECT n FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) T(n)
)

, cteTally AS (
    SELECT
        N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
    FROM
        cteTen t10
        CROSS JOIN cteTen t100
        CROSS JOIN cteTen t1000
        CROSS JOIN cteTen t10000 
)

, cteStartOfNextYear AS (
    SELECT
        StartOfNextYear = s.[Date]
        ,NumOfDaysBetween = DATEDIFF(DAY,DATEADD(YEAR,-5,s.[Date]),s.[Date])
    FROM
        (VALUES (DATEFROMPARTS(YEAR(GETDATE()) + 1, 1, 1))) s([Date])
)

, cteDates AS (
    SELECT
        [Date] = DATEADD(DAY,- t.N, s.StartOfNextYear)
    FROM
        cteStartOfNextYear s
        INNER JOIN cteTally t
        ON t.N <= NumOfDaysBetween
)

SELECT *
FROM
    cteDates
ORDER BY
    [Date]

通过我们的对话,我明白了您为什么认为EOMONTH()会解决该问题,但这是某种操作顺序。因此,在解释where子句之前,将分析整个数据集的DATEFROMPARTS()部分。因此,在将日期限制为由EOMONTH()where子句

定义的天数之前,它试图构建2月29.30等日期。

答案 1 :(得分:0)

我不知道您为什么要使用类似的代码来生成日期。为什么不从第一个日期开始,而一次只添加一个日期?

无论如何,2月29日或30日或31日将导致错误。您可以通过更改dates子查询来解决此问题:

Dates as (
    select try_convert(date, concat(year, '-' month, '-', day)) as  Date
    from Years y cross join
         Months m cross join
         Days
    where try_convert(date, concat(year, '-' month, '-', day)) and
          DAY(EOMONTH(FirstOfTheYear, Month - 1)) >= Day
)

答案 2 :(得分:0)

您要DATEFROMPARTS转换无效的日期和时间组合。这就是引发错误的原因-不是您的CAST语句。

请参阅Using T-SQL DATEFROMPARTS to return NULL instead of throw error来大致查找问题日期。

您的查询创建的日期包括2月29日,30日和31日以及4月,6月,9月和11月31日。

如果您只想获取2015年至2020年的所有日期,则可以数天,然后加上一个基准日期。 SQL Server将为您处理月份问题:

--  Create up to 16 million integers
WITH N AS (SELECT 0 AS N FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7)) T(n))
,    M AS (SELECT 0 AS N FROM N A, N B, N C, N D, N E, N F, N G, N H)
,    Z AS (SELECT ROW_NUMBER() OVER (ORDER BY A.N) AS N FROM M A)

--  Filter only the integers you need; add to a start date
SELECT CAST(DATEADD(DAY, N-1, '2015-01-01') AS DATE) FROM Z
WHERE N < DATEDIFF(DAY, '2015-01-01', '2020-01-01')