我在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:我能够通过切换到其他日期生成模式(以递归方式每天添加一天,一天)来解决我的问题,但是我仍然很好奇是什么导致了此错误。
答案 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')