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安装上的类似行为,那就太棒了。
谢谢
答案 0 :(得分:0)
发生了什么?
MySQL通过加入和排序隐式转换查询。
<强>解决方案强>
让我们先解决问题,然后讨论我们是如何实现这一目标的。请注意1/var_fld
到1.0/var_fld
和5000
到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 |
+---------------+------------+-----------+
其他解决方案
请注意用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_fld
和5000.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能够研究这个角落。如果有人遇到解释此行为的文档,请在此答案中添加注释,以便我可以自学。