更改查询顺序后的算术溢出错误

时间:2017-04-06 03:48:40

标签: sql sql-server integer-arithmetic

我们有如下查询

SELECT p.propertyNumber as propertyNumber,
       ( SELECT COUNT(*)
         FROM #TEM
         WHERE pNumber = p.propertyNumber
           AND dsenderType = 0
           AND dType = 1
           AND reTime >= '2017-03-01 00:00'
           AND reTime <= '2017-04-01 00:00' ) AS temailCount,  
       ( SELECT COUNT(*)
         FROM #TEM
         WHERE pNumber = p.propertyNumber
           AND dsenderType = 1
           AND dType = 1
           AND dSendStatus = 1
           AND sentTime >= '2017-03-01 00:00'
           AND sentTime <= '2017-04-01 00:00' ) AS temailJobCount,  
       ( SELECT SUM( DATEDIFF( second, reTime, jEnddate ) )
         FROM #TEM
         WHERE pNumber = p.propertyNumber
           AND dsenderType = 0
           AND dType = 1
           AND ( jStatus = 2 OR jStatus = 4 )
           AND jEnddate >= '2017-03-01 00:00'
           AND jEnddate <= '2017-04-01 00:00' ) AS temailturnAroundTime
FROM property p
WHERE p.locationId = 6
GROUP BY propertyNumber

抛出“将表达式转换为数据类型int的算术溢出错误”错误。但是如果我们将SUM( DATEDIFF( second, reTime, jEnddate ) )更改为SUM( DATEDIFF( minute, reTime, jEnddate ) )并且最大数字是1153447分钟(不超过70000000秒),它就会有效。

最奇怪的是,如果我们通过将SUM函数作为第二个字段来更改查询顺序,那么它是有效的(如果SUM是唯一的字段,也可以工作)

SELECT p.propertyNumber AS propertyNumber,
       ( SELECT SUM( DATEDIFF( second, reTime, jEnddate ) )
         FROM #TEM
         WHERE pNumber = p.propertyNumber
           AND dsenderType = 0
           AND dType = 1
           AND ( jStatus = 2 OR jStatus = 4 )
           AND jEnddate >= '2017-03-01 00:00'
           AND jEnddate <= '2017-04-01 00:00' ) AS temailturnAroundTime, 
       ( SELECT ......) AS temailCount,
       ( SELECT ......) AS temailJobCount
FROM property p
WHERE p.locationId = 6
GROUP BY propertyNumber

我们可以通过将DATEDIFF结果转换为bigint来解决问题,但我仍然无法弄清楚错误发生的原因。任何人都可以给我一个线索吗? 非常感谢!

1 个答案:

答案 0 :(得分:1)

当服务器逐行读取以计算SUM时,它会在内部int变量中将int值相加。如果在求和期间中间结果超过int容量(2,147,483,647),服务器将停止计算并抛出您看到的错误。

即使后续值为负数且总和将小于2,147,483,647,服务器也将停止查询执行。

显然,在查询的第一个版本中,服务器按某种顺序扫描行,这导致累积的中间结果超过2,147,483,647。其他版本的查询可能使用不同的索引来扫描行,因此,值的总和不同,从不超过2,147,483,647。

很容易重现。

我将创建一个包含聚簇索引的表,以不同的顺序添加几行并计算SUM。 在这个简单的查询中,服务器将使用聚簇索引按照此索引的顺序扫描表。

CREATE TABLE [dbo].[T](
    [ID] [int] NOT NULL,
    [Value] [int] NOT NULL,
CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))

测试1

TRUNCATE TABLE [dbo].[T];

INSERT [dbo].[T] ([ID], [Value]) VALUES (1, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (2, -2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (3, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (4, -2147483647);

SELECT SUM([Value]) AS s
FROM [dbo].[T];

<强>结果

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

+---+
| s |
+---+
| 0 |
+---+

测试2

TRUNCATE TABLE [dbo].[T];

INSERT [dbo].[T] ([ID], [Value]) VALUES (1, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (2, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (3, -2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (4, -2147483647);

SELECT SUM([Value]) AS s
FROM [dbo].[T];

<强>结果

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)
Msg 8115, Level 16, State 2, Line 8
Arithmetic overflow error converting expression to data type int.

避免此问题的最简单方法是将值转换为bigint,如您自己发现的那样:

TRUNCATE TABLE [dbo].[T];

INSERT [dbo].[T] ([ID], [Value]) VALUES (1, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (2, 2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (3, -2147483647);
INSERT [dbo].[T] ([ID], [Value]) VALUES (4, -2147483647);

SELECT SUM(CAST([Value] AS bigint)) AS s
FROM [dbo].[T];

此查询返回0而没有算术溢出错误。

发生此错误时可能会出现其他情况。

服务器可以先计算DATEDIFF(second,reTime,jEnddate)表达式的值,然后根据WHERE子句筛选出该行。

因此,如果表中有一行存在较大差异,即使稍后将其过滤掉,也可能会影响计算。

引擎不必先过滤掉行,然后再计算表达式。 您可以通过检查实际执行计划来了解正在发生的事情。

最有可能的是,当您更改查询时,计划会以这样的方式更改,即在计算表达式之前过滤掉具有较大差异的行。

这样做很容易检查:

select 
    MAX(DATEDIFF(minute,reTime,jEnddate)) AS MaxDiff,
    MIN(DATEDIFF(minute,reTime,jEnddate)) AS MinDiff
from #TEM 

请注意,此查询中没有WHERE。您希望看到全局MAXMIN差异。

相关问题