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

时间:2014-04-28 12:55:45

标签: mysql floating-point floating-accuracy floating-point-conversion

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

这是我的表:

id | rating
1  | 1317.17
2  | 1280.59
3  | 995.12
4  | 973.88

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

SELECT * FROM (`users`) WHERE `rating` > '995.12'

它正确返回2

但是,如果我尝试

SELECT * FROM (`users`) WHERE `rating` > '973.88'

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

有什么想法吗?

3 个答案:

答案 0 :(得分:17)

决定和后果

这是因为您决定使用浮点数据类型而导致的后果。浮子不准确。这意味着:是的,您可以产生> 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)

糟糕!


在屏幕后面

为什么呢?要理解为什么会这样,你应该意识到如何表示浮点数据类型。长篇大论是here。但是 - 我将简要介绍一下。

在此处如何表示:enter image description here其中:

  • s是标志
  • b 基础。它的含义与 radix
  • 相同
  • e指数

这意味着我们可以用不同的方式表示一个数字 - 这取决于我们选择的基数。最常见的是b=2。但并非所有实数都可以用这个基数精确表示,即使在十进制基数中它们看起来很好"。着名的例子是0.1 - 它不能精确地表示在b=2中 - 因此它被大致存储。同样,长篇故事你可以看到here - 但我只是注意到,用基数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的情况下,我很难记住即使是这样一个用途 - case - 如果不是大量计算的情况,可能会对性能产生影响,请参阅下面的说明)。然后还有一种方法可以让它发挥作用。您应该决定适合您的精度。那就是:从哪一点开始,你将数字视为等于。

您只存储了两位有效数字,因此我认为1E-5的精确度已经足够了。然后,您的查询将如下所示:

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

并将其用于:

SELECT * FROM t WHERE rating>973.88+@eps

将导致

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

哪个更好?

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

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


结论

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

特别是浮标不精确。是的,互联网上有很多像这样的答案,但我会重复一遍。它们精确。关于花车,你应该依赖精确度。并且 - 在几乎所有DBMS中都有定点数据类型。而且 - 在像你这样的情况下你应该使用它们。他们将完成同样的工作,但有了它们,你就可以确定选择的精度。

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

答案 1 :(得分:2)

@Hituptony是对的。试试这个:

SELECT * FROM (`users`) WHERE `rating` > 973.88

<强> SQL FIDDLE

答案 2 :(得分:1)

在比较数字时,您不需要单引号。

取下单引号,然后重试。

但是,您已经注意到这可以通过ROUND(SUM(column), 2) * 1

解决

如果单引号不起作用,请将其与此^^

的值进行比较

请参阅链接:https://dev.mysql.com/doc/refman/5.0/en/problems-with-float.html