0

MySQL 5.7(特别是 5.7.13-0ubuntu0.16.04.2 )中的结果非常奇怪。

我怀疑这可能是 MySQL 中的错误。

DROP TABLE IF EXISTS `test_grids_1`;
CREATE TABLE `test_grids_1` (
  `unq_id` int(11) NOT NULL DEFAULT '0',
  `var_fld` int(11) DEFAULT '0'
) ENGINE=InnoDB;
INSERT INTO `test_grids_1` VALUES
  (1,4500),
  (2,6000);

DROP TABLE IF EXISTS `test_grid_dtl_1`;
CREATE TABLE `test_grid_dtl_1` (
  `dtl_id` int(11) NOT NULL DEFAULT '0',
  `unq_id` int(11) DEFAULT '0',
  `dtl_var` decimal(14,2) DEFAULT '0.00'
) ENGINE=InnoDB;
INSERT INTO `test_grid_dtl_1` VALUES
  (1,1,2.00),
  (2,1,2.40),
  (3,2,2.30);


SELECT
  ( g.calc_var * d.dtl_var ) new_var,
  g.calc_var
FROM
  (
  SELECT
    unq_id,
    IF ( var_fld  > 5000, ( 1 / var_fld ) , 5000 ) calc_var
  FROM
    test_grids_1
  ) g
INNER JOIN
  test_grid_dtl_1 d
    ON d.unq_id = g.unq_id;

+--------------+----------+
| new_var      | calc_var |
+--------------+----------+
| 10000.000000 |     5000 |
| 12000.000000 |     5000 |
|     0.000383 |   0.0002 |
+--------------+----------+

SELECT
  ( g.calc_var * d.dtl_var ) new_var,
  g.calc_var
FROM
  (
  SELECT
    unq_id,
    IF ( var_fld  > 5000, ( 1 / var_fld ) , 5000 ) calc_var
  FROM
    test_grids_1
  ) g
INNER JOIN
  test_grid_dtl_1 d
    ON d.unq_id = g.unq_id
ORDER BY
  1;

+--------------+----------+
| new_var      | calc_var |
+--------------+----------+
|     0.000383 |   0.0002 |
| 10000.000000 |  99.9999 |
| 12000.000000 |  99.9999 |
+--------------+----------+
3 rows in set (0.00 sec)

当包含排序时,它会导致某些标准的返回值完全不正确。

预期为 5000 的值突然变为 99.9999。

如果有人可以检查并确认其他 5.7 安装上的类似行为,那就太好了。

谢谢

4

1 回答 1

0

这是怎么回事?

MySQL 通过加入和排序进行隐式转换,查询正在被位。

解决方案

让我们先解决问题,然后讨论我们是如何到达那里的。注意1/var_fldto1.0/var_fld5000to的变化5000.0

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1.0/var_fld, 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+
| new_var       | calc_var   |
+---------------+------------+
|     0.0003833 |    0.00017 |
| 10000.0000000 | 5000.00000 |
| 12000.0000000 | 5000.00000 |
+---------------+------------+

您也可以使用cast. 请注意,我在最后一列中包含了十六进制值。当您继续阅读时,它将很有用:

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, cast(1/var_fld as decimal(15,5)), 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+-----------+
| new_var       | calc_var   | hcalc_var |
+---------------+------------+-----------+
|     0.0003910 |    0.00017 | 0         |
| 10000.0000000 | 5000.00000 | 1388      |
| 12000.0000000 | 5000.00000 | 1388      |
+---------------+------------+-----------+

其他解决方案

注意ifwithcase语句的替换。

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, case when var_fld > 5000 then 1/var_fld else 5000 end calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+--------------+-----------+
| new_var      | calc_var  |
+--------------+-----------+
|     0.000383 |    0.0002 |
| 10000.000000 | 5000.0000 |
| 12000.000000 | 5000.0000 |
+--------------+-----------+

请注意case语句如何不需要任何类型的转换即可获得几乎相同的结果。但是,要获得与第一个查询完全相同的结果,您必须执行以下操作 -

还有一个

注意1.0/var_fldand5000.0和 withcast而不是if

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, case when var_fld > 5000 then 1.0/var_fld else 5000.0 end calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

这是如何突出的?

让我们看一下原始查询;我添加了一个新字段hex(g.calc_var),它是g.calc_var.

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1/var_fld, 5000) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+--------------+----------+-----------+
| new_var      | calc_var | hcalc_var |
+--------------+----------+-----------+
|     0.000383 |   0.0002 | 0         |
| 10000.000000 |  99.9999 | 1388      |
| 12000.000000 |  99.9999 | 1388      |
+--------------+----------+-----------+

将结果与解决方案部分中的第一个查询进行比较

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1.0/var_fld, 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+-----------+
| new_var       | calc_var   | hcalc_var |
+---------------+------------+-----------+
|     0.0003833 |    0.00017 | 0         |
| 10000.0000000 | 5000.00000 | 1388      |
| 12000.0000000 | 5000.00000 | 1388      |
+---------------+------------+-----------+

请注意,两个查询中的十六进制值完全相同,但十进制值不同。

5000 怎么会变成 99.9999?

select cast(5000 as decimal(6,4)) as test;
+---------+
| test    |
+---------+
| 99.9999 |
+---------+
1 row in set, 1 warning (0.00 sec)

show warnings;
+---------+------+-----------------------------------------------+
| Level   | Code | Message                                       |
+---------+------+-----------------------------------------------+
| Warning | 1264 | Out of range value for column 'test' at row 1 |
+---------+------+-----------------------------------------------+

像那样!当 5000 转换为长度为 6 的小数(包括 4 个小数)时,结果是适合小数 (6,4) 的最大值。哎哟。

在这种情况下,会引发警告,这很好。在测试过程中可能会发现它。但是,有问题的查询不会引发任何警告。这不好。

这导致了多个问题

  • 为什么在没有 的情况下正确进行投射order by
  • 是什么order by导致铸造以我们注意到的方式发生?
  • 为什么case...end显示的结果比if(...)不进行强制转换时效果更好?
  • 为什么在完成这样的投射时不会发出警告?

您可能想向 MySQL 人员提交错误报告。我没有安装最新的 MariaDB,所以我不能说 MariaDB 中是否也存在这个问题。我很好奇并安装了 10.0.25-MariaDB-0ubuntu0.16.04.1。MariaDB 10.0.25 中似乎也存在同样的问题。

这可以预防吗?

是的。每当处理 int 到 float/double/decimal 时,隐式转换以获得可预测的结果。我仍然希望 MySQL 能够研究这种极端情况。如果有人遇到解释此行为的文档,请在此答案中添加评论,以便我进行自我教育。

于 2016-08-20T04:23:05.080 回答