【问题标题】:What happens if SQL sum() reach type capacity (overflow)?如果 SQL sum() 达到类型容量(溢出)会怎样?
【发布时间】:2016-03-25 15:50:12
【问题描述】:

我知道这个问题取决于供应商,但我应该担心像 SUM 这样的聚合函数是否在小型类型上运行?

例如 MariaDB 使用 4 个字节来表示类型 INT。开发者可能会假设每笔交易的金额不超过几千。

但是,如果我们尝试为所有部门获取一整年的收入,会发生什么?例如:

-- CREATE TABLE income (dt DATETIME, department INT, amount INT);
SELECT SUM(amount) FROM income WHERE dt BETWEEN '2014-01-01' and '2014-12-31'

增加存储大小只是为了修复聚合函数SUM的溢出问题看起来很愚蠢。

我应该担心什么? SQL 92/99/2008 标准是否有任何保证或澄清?

JDBC 驱动程序有什么特殊支持吗?

我应该重写表单中的选择吗:

SELECT SUM(CAST(amount AS BIGINT)) FROM income
  WHERE dt BETWEEN '2014-01-01' and '2014-12-31'

【问题讨论】:

  • 我想你回答了你自己的问题。是的,它是特定于供应商的,并且转换为 bigint 会有所帮助。你能溢出一个bigint吗?理论上。所以你需要,根据你将要存储的数据,你可能会溢出它吗?
  • @a_horse_with_no_name 感谢指正,已修复!如果 SQL 服务器引擎句柄溢出,我应该在客户端做什么?我应该在 JDBC/ODBC 客户端代码中选择更大的数据类型吗?
  • 在客户端你必须考虑到这一点,是的。
  • @SQLOTL:除了float 是一种近似 数据类型,您永远无法确保检索到的值与您存储的相同。它应该绝不用于任何金融操作。 floating-point-gui.de
  • @a_horse_with_no_name 是的,我知道这些关于decimalfloat 的讨论。我也非常了解技术和数学基础知识。不过,我觉得这很荒谬。也许对于银行来说,好的。但是我们有一个海量数据报告系统,我们需要快速实时处理大额和各种计算。因此我总是使用更快的float。结果四舍五入为 2 位数。 25 年来,我从未遇到过问题。相信我,我确实有与大公司打交道的经验。

标签: sql integer-overflow


【解决方案1】:

在 mysql 上测试相当容易:

32位溢出:

mysql> select sum(x) from (
    select pow(2,31) as x
    union all
    select pow(2,31)
    union all
    select pow(2,31)
) as bignums;
+------------+
| sum(x)     |
+------------+
| 6442450944 | // returned as a "bigint"
+------------+
1 row in set (0.00 sec)

64位:

mysql> select sum(x) from (
    select pow(2,63) as x
    union all
    select pow(2,63)
    union all
    select pow(2,63)
) as bignums;
+-----------------------+
| sum(x)                |
+-----------------------+
| 2.7670116110564327e19 | // returned as float
+-----------------------+
1 row in set (0.00 sec)

双:

mysql> select sum(x) from (
    select 1.7e+308 as x
    union all
    select 1.7e+308
    union all
    select 1.7e+308
) as bignums;
+--------+
| sum(x) |
+--------+
|      0 |
+--------+

在 mysql 上测试相当容易:

32位溢出:

mysql> select sum(x) from (
    select pow(2,31) as x
    union all
    select pow(2,31)
    union all
    select pow(2,31)
) as bignums;
+------------+
| sum(x)     |
+------------+
| 6442450944 | // returned as a "bigint"
+------------+
1 row in set (0.00 sec)

64位:

mysql> select sum(x) from (
    select pow(2,63) as x
    union all
    select pow(2,63)
    union all
    select pow(2,63)
) as bignums;
+-----------------------+
| sum(x)                |
+-----------------------+
| 2.7670116110564327e19 | // returned as float
+-----------------------+
1 row in set (0.00 sec)

双:

mysql> select sum(x) from (
    select 1.7e+308 as x
    union all
    select 1.7e+308
    union all
    select 1.7e+308
) as bignums;
+--------+
| sum(x) |
+--------+
|      0 |
+--------+

评论跟进:

mysql> describe overflow
    -> ;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| x     | int(11)    | YES  |     | NULL    |       |
| y     | bigint(20) | YES  |     | NULL    |       |
| z     | double     | YES  |     | NULL    |       |
+-------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> select * from overflow;
+------------+---------------------+---------+
| x          | y                   | z       |
+------------+---------------------+---------+
| 2147483647 | 9223372036854775807 | 1.7e308 |
| 2147483647 | 9223372036854775807 | 1.7e308 |
| 2147483647 | 9223372036854775807 | 1.7e308 |
+------------+---------------------+---------+
3 rows in set (0.00 sec)

mysql> select sum(x), sum(y), sum(z) from overflow;
+------------+----------------------+--------+
| sum(x)     | sum(y)               | sum(z) |
+------------+----------------------+--------+
| 6442450941 | 27670116110564327421 |      0 |
+------------+----------------------+--------+
1 row in set (0.00 sec)

【讨论】:

  • pow(2,31) 返回什么数据类型? bigint 还是 int ?
  • 好吧,如果我硬编码 2147483648 而不是 pow(...),我会得到完全相同的值输出。 pow(2,63) 确实返回一个浮点数。
  • 嗯。保存另一个编辑时遇到问题,但硬编码 2^63 值返回 27670116110564327424 ,这超过了 64 位 int 的最大值。非常有趣/奇怪。
  • 嗯嗯,但是,pow(2,31) 不会返回一个 4 字节的int,所以情况与 OP 所要求的不同。为了正确的测试,我们应该建立一个数据类型为int的测试表,并至少填写两条记录,这样在求和时就会发生溢出。
  • 参见上面的最后一次编辑。建立了一个新表,并且基本上得到了相同的结果。看起来它只是“向上”失败到更大的容量类型,直到它达到真正的上限并失败为“0”。
【解决方案2】:

Postgres 处理这个没有溢出或截断:

来自手册:

sum(expression),返回类型:bigint 表示 smallint 或 int 参数,numeric 表示 bigint 参数,否则与参数数据类型相同

http://www.postgresql.org/docs/current/static/functions-aggregate.html

快速测试证明:

psql (9.4.5)
Type "help" for help.

postgres=> create table x (amount int);
CREATE TABLE
postgres=>
postgres=> insert into x values (2147483647), (2147483647);
INSERT 0 2
postgres=> select sum(amount)
wbtest-> from x;
    sum
------------
 4294967294
(1 row)

postgres=>

有趣的是,SQL 标准要求语句在这种情况下失败

如果在计算 AF 结果期间,中间结果不能以包含该中间结果的站点的声明类型表示,则
...
否则,会引发异常情况:数据异常 — 数值超出范围。

(AF = 聚合函数)

【讨论】:

    【解决方案3】:

    当我理解你的正确时,你问的是如果溢出会发生什么。

    至少对于 SQL Server,请查阅此文档:

    https://msdn.microsoft.com/de-de/library/ms187810%28v=sql.120%29.aspx

    这里说明sum() 的返回类型对于特定输入类型是什么:

    Expression result               Return type
    ------------------------------------------------
    tinyint                         int
    smallint                        int
    int                             int
    bigint                          bigint
    decimal category (p, s)         decimal(38, s)
    money and smallmoney category   money
    float and real category         float 
    

    这意味着,可能确实存在溢出。所以我建议你使用floatmoney 来表示薪水,而不是int

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-30
    • 2020-10-15
    • 2020-07-11
    • 2017-09-26
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    相关资源
    最近更新 更多