MySQL"大于"条件有时会返回具有相等值的行

jov*_*van 6 mysql floating-point floating-accuracy floating-point-conversion

我遇到了一个基本的MySQL查询令人困惑的问题.

这是我的表:

id | rating
1  | 1317.17
2  | 1280.59
3  | 995.12
4  | 973.88
Run Code Online (Sandbox Code Playgroud)

现在,我试图找到rating列大于某个值的所有行.如果我尝试以下查询:

SELECT * FROM (`users`) WHERE `rating` > '995.12'
Run Code Online (Sandbox Code Playgroud)

它正确返回2.

但是,如果我尝试

SELECT * FROM (`users`) WHERE `rating` > '973.88'
Run Code Online (Sandbox Code Playgroud)

它回来了4!所以就好像它认为表中的973.88大于973.88,但它与995.12没有同样的错误.无论我是从PHP脚本还是在phpMyAdmin中运行查询,都会发生这种情况.

有任何想法吗?

Alm*_* Do 19

决定和后果

这是因为您决定使用浮点数据类型而产生的后果.浮子不准确.这意味着:是的,你可以得到一个> a = true

例如,你的第四行:

mysql> SELECT *  FROM t WHERE id=4;
+------+--------+
| id   | rating |
+------+--------+
|    4 | 973.88 |
+------+--------+
1 row in set (0.00 sec)

我已经发布了数据类型,就是这样FLOAT.我们到了:

mysql> SELECT rating>973.88 FROM t WHERE id=4;
+---------------+
| rating>973.88 |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)

哎呀!


在屏幕后面

为什么?要理解为什么会这样,你应该意识到如何表示浮点数据类型.长话故事就在这里.但是 - 我将简要介绍一下.

它是如何表示的: 在此输入图像描述 哪里:

  • s 是标志
  • b基地.它的意思与基数相同
  • e指数.

这意味着我们可以用不同的方式表示一个数字 - 这取决于我们选择的基数.最常见的是b=2.但并非所有实数都可以用这个基数精确表示,即使在十进制基数中它们看起来"好".着名的例子是0.1- 它不能b=2精确地表示- 因此它被大致存储.同样,你可以在这里看到长篇故事- 但我要注意,用基数2精确地表示它是不可能的.

结果是:即使数字在十进制基数中是精确的,它仍然可能无法精确地表示它 - 因此,它将被大致存储.它是如何工作的,事实上,这是有意的 - 因为浮动本身的结构.


该怎么办

固定精度

嗯,首先,你应该问问自己:你真的需要漂浮吗?注意:我说:漂浮.因为 - 还有固定点数.它们将以固定的精度表示数字.说起来很简单:使用定点数据类型,您可以确定您将准确存储您在屏幕上看到的内容.所以,如果它973.88- 那么它是,973.88而不是973.8800000439234.转向交易:

mysql> ALTER TABLE t CHANGE rating rating DECIMAL(8,2);
Query OK, 4 rows affected, 4 warnings (0.47 sec)
Records: 4  Duplicates: 0  Warnings: 4

和..

mysql> SELECT rating>973.88 FROM t WHERE id=4;
+---------------+
| rating>973.88 |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)

TADA!魔术发生了.您的号码现在以固定的精度存储,因此,此类比较失败.

运用 float

然后,当你遇到浮动时可能存在用例(但是,在DBMS的情况下,我很难记住甚至一个这样的用例 - 如果不是大量计算的情况,这可能会导致性能影响,见下面的说明).然后还有一种方法可以让它发挥作用.您应该决定适合您的精度.那就是:从哪一点开始,你将数字视为等于.

你只存储两个有效数字,所以我认为精度1E-5会绰绰有余.然后,您的查询将如下所示:

mysql> set @eps=1E-5;
Query OK, 0 rows affected (0.00 sec)

并使用它:

SELECT * FROM t WHERE rating>973.88+@eps
Run Code Online (Sandbox Code Playgroud)

这将导致

+------+---------+
| id   | rating  |
+------+---------+
|    1 | 1317.17 |
|    2 | 1280.59 |
|    3 |  995.12 |
+------+---------+

哪个更好?

要实现这一点,您需要再次查看封面.我简要概述了float数据类型是什么以及为什么它不准确.但是,fixed数据类型也有它的弱点.可能在DBMS的上下文中我们不应该担心它,但我会提到它:fixed一般来说,数据类型会对性能产生影响.这取决于您在DBMS中将进行多少计算.

在MySQL中,fixed-point数据类型(例如DECIMAL)被实现为BCD字符串(所以长话短说 - 再次,这里是wiki链接).这意味着相比之下float会导致性能问题.但是如果你不经常在DBMS中进行计算,那么这种影响就不会引人注意了 - 我之所以提到它,因为浮点数和定点都有自己的问题.


结论

与所有其他计算机一样,DBMS并不完美.它只是使用一些内部事物来完成工作.这意味着:在某些情况下,你必须意识到内部事物如何运作才能理解为什么你会得到一些奇怪的结果.

特别是浮子不精确.是的,互联网上有很多这样的答案,但我会再说一遍.它们准确.你应该依赖于精确的时候它是关于浮动.并且 - 在几乎所有DBMS中都有定点数据类型.而且 - 在像你这样的情况下你应该使用它们.他们将做同样的工作,但有了它们你就会确定选择的精度.

但是,您可能希望使用浮点数 - 如果您要在DBMS中进行太多计算.但是,另一方面,这是关于 - 你为什么要这样做?为什么不使用应用程序来产生这些计算(因此,避免使用定点数据类型的性能影响和浮点数的预定问题 - 因为使用具有平均计算量的定点是可以的)