8

我在基本的 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 中运行查询,都会发生这种情况。

有任何想法吗?

4

3 回答 3

22

决定和后果

这是因为您决定使用浮点数据类型而导致的后果。浮点数不精确。这意味着:是的,您可以导致 a>a = true

例如,您的第四行:

mysql> SELECT * FROM t WHERE id=4;
+--------+--------+
| 编号 | 评级 |
+--------+--------+
| 4 | 973.88 |
+--------+--------+
一组中的 1 行(0.00 秒)

我已经留下了您发布的数据类型,它是FLOAT. 我们到了:

mysql> 选择等级>973.88 FROM t WHERE id=4;
+---------------+
| 评分>973.88 |
+---------------+
| 1 |
+---------------+
一组中的 1 行(0.00 秒)

哎呀!


屏幕后面

为什么?要理解为什么会这样,您应该了解浮点数据类型是如何表示的。长篇大论就在这里。但是 - 我将简要概述一下。

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

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

这意味着我们可以用不同的方式表示一个数字——这取决于我们将选择哪个基数。最常见的是b=2. 但并非所有实数都可以用这个底数精确表示,即使在十进制底下它们看起来“不错”。著名的例子是0.1- 它不能b=2精确地表示 - 所以它是近似存储的。再说一次,你可以在这里看到长篇大论- 但我只想指出,不可能用基数 2 精确地表示它。

结果是:即使数字在十进制基数中是精确的,它仍然可能无法精确表示 - 因此,它将被近似存储。这就是它的工作原理,事实上,这是有意的——因为浮动本身的结构。


该怎么办

固定精度

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

mysql> ALTER TABLE t CHANGE rating rating DECIMAL(8,2);
查询正常,4 行受影响,4 个警告(0.47 秒)
记录:4 重复:0 警告:4

和 ..

mysql> 选择等级>973.88 FROM t WHERE id=4;
+---------------+
| 评分>973.88 |
+---------------+
| 0 |
+---------------+
一组中的 1 行(0.00 秒)

多田!魔术发生。您的号码现在以固定精度存储,因此,这种比较失败。

使用float

然后,当您遇到浮点数时,可能会有用例(但是,在 DBMS 的情况下,我什至很难记住一个这样的用例 - 如果不是大量计算的情况,这可能会导致性能影响,请参见下面的描述)。然后还有办法让它工作。您应该决定适合您的精度。那就是:从哪一点开始,您将平等对待数字。

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

mysql> 设置@eps=1E-5;
查询正常,0 行受影响(0.00 秒)

并将其用于:

SELECT * FROM t WHERE rating>973.88+@eps

这将导致

+--------+---------+
| 编号 | 评级 |
+--------+---------+
| 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 中进行太多计算。但是,另一方面,那是关于 - 你为什么要这样做?为什么不使用应用程序来产生这些计算(因此,避免使用定点数据类型的性能影响和浮点数的精度问题 - 因为使用具有平均计算量的定点是可以的)

于 2014-04-28T13:40:01.060 回答
2

@Hituptony 是正确的。试试这个:

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

SQL 小提琴

于 2014-04-28T13:05:41.237 回答
1

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

去掉单引号,然后重试。

但是,这已被指出为您可以解决的问题ROUND(SUM(column), 2) * 1

如果单引号不起作用,请将其与此值进行比较^^

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

于 2014-04-28T13:02:40.600 回答