【问题标题】:Arithmetic overflow error converting expression to data type datetime sql将表达式转换为数据类型 datetime sql 的算术溢出错误
【发布时间】:2017-09-22 19:19:36
【问题描述】:

我不知道我在哪里搞砸了。完整代码:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

INSERT INTO @Table 
    SELECT 
        MIN(dt), MAX(dt), w
    FROM
        (SELECT 
             dt, year(dt) y, DATEPART(week, dt) w
         FROM
             (SELECT 
                  @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
              FROM 
                  sys.columns s1 
              CROSS JOIN
                  sys.columns s2) q
         WHERE 
             dt BETWEEN @start_date AND @end_date) a
GROUP BY 
    y, w

我已经缩小了错误发生的范围。它发生在这里:

SELECT 
    dt, year(dt) y, DATEPART(week, dt) w
FROM
    (SELECT 
         @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
     FROM 
         sys.columns s1 
     CROSS JOIN
         sys.columns s2) q
WHERE 
    dt BETWEEN @start_date AND @end_date

如果我只运行这部分,我会收到错误:

将表达式转换为数据类型日期时间时出现算术溢出错误。

我尝试在这里和那里添加一些CONVERT 试图找出它在哪里搞砸了,但我无法弄清楚。

我见过其他几个人遇到类似问题,但this one 或this one 都没有任何对我有帮助的解决方案。

我是基于集合的 SQL 编程的新手,我是循环自学的,所以这超出了我的深度。任何帮助表示赞赏。

【问题讨论】:

  • 您希望@start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) 增加一天吗?
  • @scsimon 确实如此。如果我只运行那个内部选择,我会得到一年中每一天的列表,从 @Start_Date 开始,一直持续到我停止查询。
  • 我知道它的作用,我在问你是否期望它。它不适用于日期,但可以在datetime 上使用,但出于明确的原因和代码管理,我建议坚持使用dateadd()。很多人滥用这个。
  • 我运行了您的代码,并提供了一个指向上述在线版本的链接,因此使用该 sn-p 它是不可重现的。语法为 dateadd(day, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1),@start_date),因为您似乎需要表格中的 RN
  • 尝试并测试它的不同部分。为@start_date + DATEADD(day, 1, @start_date 的每个部分执行单独的select 语句。显然,第一部分将只显示@start_date is,但第二部分是做什么的?您正在尝试为其添加一天,然后当前将其添加回自身。此外,您知道问题出在哪里,并且您也知道问题出在哪里(在select 语句中)。摆脱 where 子句,是否摆脱它?不?将 select 替换为 select *。现在怎么样?代码很小,所以摆脱或隔离找到它。

标签: sql-server datetime


【解决方案1】:

ROW_NUMBER() 返回一个 bigint 值,但将天数添加到日期仅限于 int;因此,如果交叉连接产生的行数超过 int 的最大值,您将产生该错误消息。

sys.columns 中的行数只需超过代码中使用的笛卡尔积的平方根 2,147,483,647 (>=46,341) 即可产生该错误。通过重复交叉连接,我能够在更小的 sys.columns 表上重现相同的错误。例如:

  SELECT 
      @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
  FROM 
      sys.columns s1 
  CROSS JOIN
      sys.columns s2
  CROSS JOIN
      sys.columns s3
  CROSS JOIN
      sys.columns s4

避免该问题的一种方法是不要将 row_number() 添加到内部查询中的日期,只需返回 row_number() 并使用 where 子句来避免错误,例如:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

INSERT INTO @Table 
    SELECT 
        MIN(dt), dateadd(day,7,MIN(dt)), w
    FROM
        (SELECT 
             dateadd(day,q.i,@start_date) dt, year(dateadd(day,q.i,@start_date)) y, DATEPART(week, dateadd(day,q.i,@start_date)) w
         FROM (
             SELECT 
                  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 i
              FROM 
                  sys.columns s1 
              CROSS JOIN
                  sys.columns s2
             ) q
         WHERE 
             i <= datediff(day,@start_date,@end_date) 
         ) q2
GROUP BY 
    y, w
;

请注意,有很多选择,特别是当您真的只想要周范围时。您可以使用永久的“计数表”而不是 sys.columns,或者使用 10 行 CTE 的重复交叉连接来创建所需的行。

这个问题的公认答案Tally Table to insert missing dates between two dates? SQL 是使用永久数字表(又名“计数表”)的示例。另请注意,通过这样做,您可以每行添加 7 天,从而避免将这些天分组为几周。

这是一个使用 CTE 的重复交叉连接的示例:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

;WITH
cteDigits AS (
    SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
    SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
cteTally AS (
    SELECT [100s].digit * 100 + [10s].digit * 10 + [1s].digit AS i
    FROM cteDigits [1s]
    CROSS JOIN cteDigits [10s]
    CROSS JOIN cteDigits [100s]
)
SELECT
      dateadd(day,q.i*7,@start_date) startDate
    , dateadd(day,(q.i*7)+7,@start_date) endDate
    , year(dateadd(day,q.i*7,@start_date)) y
    , DATEPART(week, dateadd(day,q.i*7,@start_date)) w

FROM cteTally q
WHERE dateadd(day,(q.i*7)+7,@start_date) <= @end_date
ORDER BY q.i

【讨论】:

  • 投反对票的人能否指出我未能解释的内容?问题是他们为什么会收到特定的错误消息。该错误消息的原因是原始查询生成的笛卡尔积大于 MSSQL 中 int 的最大大小。这很容易重现。我还确定了几种避免该问题的方法。不知道我错过了什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-11
  • 2011-10-07
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多