MySQL 5.7排序/子查询错误的结果

时间:2016-08-19 16:44:15

标签: sorting mysql-5.7

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安装上的类似行为,那就太棒了。

谢谢

1 个答案:

答案 0 :(得分:0)

发生了什么?

MySQL通过加入和排序隐式转换查询。

<强>解决方案

让我们先解决问题,然后讨论我们是如何实现这一目标的。请注意1/var_fld1.0/var_fld50005000.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      |
+---------------+------------+-----------+

其他解决方案

请注意用if语句替换case

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_fld5000.0以及cast而不是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能够研究这个角落。如果有人遇到解释此行为的文档,请在此答案中添加注释,以便我可以自学。