【问题标题】:SQL: "Cannot construct data type date" when comparing two datesSQL:比较两个日期时“无法构造数据类型日期”
【发布时间】:2019-10-09 03:24:17
【问题描述】:

我在 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 年的第一天,查询中的 10 月日期)并针对这些日期进行查询不会导致显示错误。

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

Edit2:我能够通过切换到不同的日期生成模式(以递归方式添加一天、一天一天)来解决我的问题,但我仍然很好奇导致此错误的原因。

【问题讨论】:

    标签: sql sql-server date


    【解决方案1】:

    其他几个答案的要点是,以您的方式解决问题不一定是生成日期表的最有效方式。大多数情况下,当受到 SQL 服务器的限制时,人们会引导某人为此目的使用 Tally 表。这样做将保持基于 SET 的操作,而不是需要循环或递归。这意味着您在其中一个 cmets 中提到的递归限制根本不适用。

    Tally 表是一组数值,您可以使用这些数值来生成或生成所需的值。在这种情况下,大约为 1827 天(5 年 + 1 天),但闰年可能会有所不同。闰年和二月可能是您代码中的问题。无论如何,要生成一个计数表,您可以从 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 等日期。

    【讨论】:

    • 我不知道有多少次我必须告诉人们我已经验证 CTE 正在处理 2 月和 30 天的月份没有问题(可通过注释掉 WHERE 子句来验证)。但我很欣赏计数表如何在没有递归的情况下工作的细分,并为此给出了 +1。
    • @livingparadox 也许你应该问自己的问题是人们需要告诉你多少次二月、30 天月和闰年是你的问题?采用与上面相同的查询并将 31 更改为 28 并且代码有效。所以你的问题显然在于二月等。
    • 好吧,将 31 更改为 28 确实消除了错误,即使最后使用了 where 子句。知道为什么它 only 用 where 子句抛出错误,但在它不存在时不抛出它?
    • @livingparadox 是的,我也只是在查看执行计划,因为我明白为什么你会认为 EOMONTH() 会解决这个问题,但它是一种操作顺序。因此,在解释 where 子句之前,对整个数据集的 DATEFROMPARTS() 部分进行了分析。因此,它试图在将其限制为 EOMONTH() where 子句定义的天数之前构建 2 月 29,30 日等日期
    • 啊,好吧。您能否在答案中添加该解释?我已接受您的回答,因为您都提供了我正在寻找的解释和替代方法。
    【解决方案2】:

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

    无论如何,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
    )
    

    【讨论】:

    • 日期生成代码是为了避免使用递归限制,我必须这样做才能涵盖整整五年的日期。我的代码已经使用 EOMONTH 函数处理了二月(包括闰年),该函数获取特定月份和年份的月份的最后一天。正如我的问题中所述,如果我不使用 WHERE 子句,查询已经正常运行;只有在比较日期时才会引发错误。
    【解决方案3】:

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

    请参阅Using T-SQL DATEFROMPARTS to return NULL instead of throw error 了解您的问题日期。

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

    如果您只想要从 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')
    

    【讨论】:

    • 我的代码已经使用 EOMONTH 函数处理了二月(包括闰年),该函数获取特定月份和年份的月份的最后一天。如我的问题所述,如果我不使用 WHERE 子句,查询已经运行且没有错误;只有在比较日期时才会引发错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多