我一直致力于提高MySQL查询的效率和速度。我有一个需要从一堆行中总结一列的地方。根据用户舍入首选项计算和舍入列,因此舍入方法使用参数。
我发现使用参数会生成一个引用因子,这会导致round函数像FLOOR而不是CEILING。
您可以在此示例中轻松查看:
mysql> SELECT ROUND(1.945,2), ROUND((ROUND((7002) / '1') * '1') / 3600,2) AS param_rounded, ROUND((ROUND((7002) / 1) * 1) / 3600,2) AS hard_rounded;
+----------------+---------------+--------------+
| ROUND(1.945,2) | param_rounded | hard_rounded |
+----------------+---------------+--------------+
| 1.95 | 1.94 | 1.95 |
+----------------+---------------+--------------+
7002
值是来自我的数据的实际工作示例(它实际上也是一个计算值)和7200/3600 == 1.945
。您可以看到param_rounded
使用'1'
(引用)因素会导致舍入不正确。这就是我发生的事情,因为我总是使用参数化查询。 hard_rounded
是我现在正在做的事情,首先确认因子是适当的值(它们来自数据库整数字段,所以我不担心它们的注入)并将它们直接插入到SQL字符串中
修改 在参数中使用适当的数据类型会导致正确的舍入。我在我用于查询的库中找到了不正确的参数类型。
但是,我认为这并不重要,因为在MySQL的最后一轮提供的实际数字是正确的 - 1.945。除法和乘法因子发生在最后一轮之前,所以我给MySQL的工作是ROUND(1.945)
,它返回错误。如果输出没有最后一轮的因子,则得到的列结果为1.945。
答案 0 :(得分:1)
我不确定我的含义,但在手册中他们说:
对于精确值数字,ROUND()使用“从零开始的一半” 或“向最近的方向”规则:具有0.5的小数部分的值 如果为正数或向下,则向上舍入到下一个整数 下一个整数,如果是负数。 (换句话说,它是圆形的 零。)小数部分小于.5的值向下舍入到 如果为正,则为下一个整数;如果为负,则为下一个整数。
我感觉不好将手册链接到5k Rep OP,但是这里有关于round()函数的数据类型的信息。没有什么我可以完全理解,但它可能会帮助你搞清楚。 => ROUND(X,d)
https://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html#function_round
还可以看到关于
的示例对于近似值数字,结果取决于C库。
答案 1 :(得分:0)
问题的一部分陈述:
我发现使用参数会生成引用因子
因此,我猜测查询不一定是这样编写的,但引号是由查询处理器自动添加的。即使查询处理器没有添加引号,获得的输出也类似于显式添加引号时可能看到的行为。因此,在某种程度上,暗示所获得的结果比OP部分的一些粗心玩法更隐蔽。但是,还有待观察。如果没有可验证的例子,我也不想进一步推动辩论或评论。所以,这就是......
计划1:
$servername = "localhost";
$username = "test";
$password = "test";
$dbname = "testdb";
$array = array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare("SELECT ROUND(1.945,2) AS test_round,
ROUND((ROUND((7002) / ?) * ?) / 3600,2) AS param_rounded,
ROUND(
(7002 / ?)* ?
/ 3603,5
)AS param_rounded_5_places,
ROUND(
(7002 / ?)* ?
/ 3603,4
)AS param_rounded_4_places,
ROUND(
(7002 / ?)* ?
/ 3603,3
)AS param_rounded_3_places,
ROUND((ROUND((7002) / 1) * 1) / 3600,2) AS hard_rounded,
ROUND((ROUND((7002) / CAST(? AS DECIMAL(10,2))) * CAST(? AS DECIMAL(10,2))) / 3600,2) AS param_rounded_modified"
);
$stmt->bindParam(1, $array[0]);
$stmt->bindParam(2, $array[1]);
$stmt->bindParam(3, $array[2]);
$stmt->bindParam(4, $array[3]);
$stmt->bindParam(5, $array[4]);
$stmt->bindParam(6, $array[5]);
$stmt->bindParam(7, $array[6]);
$stmt->bindParam(8, $array[7]);
$stmt->bindParam(9, $array[8]);
$stmt->bindParam(10, $array[9]);
$stmt->execute();
var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
}
catch(PDOException $e){
echo "Diagnostic: ".$e;
}
计划2:
这个程序与第一个程序完全相同,除了:
$stmt->bindParam(1, $array[0], PDO::PARAM_INT);
$stmt->bindParam(2, $array[1], PDO::PARAM_INT);
$stmt->bindParam(3, $array[2], PDO::PARAM_INT);
$stmt->bindParam(4, $array[3], PDO::PARAM_INT);
$stmt->bindParam(5, $array[4], PDO::PARAM_INT);
$stmt->bindParam(6, $array[5], PDO::PARAM_INT);
$stmt->bindParam(7, $array[6], PDO::PARAM_INT);
$stmt->bindParam(8, $array[7], PDO::PARAM_INT);
$stmt->bindParam(9, $array[8], PDO::PARAM_INT);
$stmt->bindParam(10, $array[9], PDO::PARAM_INT);
现在,大多数用户习惯于以前一种方式编写查询:
$stmt->bindParam(1, $array[0]);
这可能适用于大多数算术运算(至少在我个人的经验中),但ROUND
似乎突显了一个可能的问题。不合需要的截断/舍入......
Computed Expected Obtained
1.945 1.95 1.94
Demo - 虽然它只是一个MySQL查询,但行为与PDO
完全相同。
一个有趣的观察:
或许有必要检查整个事物的表现形式与重复的分数或类似情况:param_rounded_5_places
和param_rounded_4_places
基本上计算为7002 / 3603 ===> 1.943380516...
Computed Expected Obtained
param_rounded 1.945 1.95 1.94 --> Rounded down
param_rounded_5_places 1.943380516 1.94338 1.94338
param_rounded_4_places 1.943380516 1.9434 1.9434 --> NOT Rounded down
可以看到with quotes
(并且在没有指定数据类型的情况下使用PDO bindParam()
),当精度限制在小数点后的两个位置时问题仍然存在,但似乎消失了(至少在这种情况下)当检查精度超过小数点后第二位时。这是一个问题吗?我不知道......
虽然为了这个答案的目的,我还没有使用mysqli_*()
PHP函数运行相同的测试,但很可能MySQLI Prepared Queries的行为方式也相同。
FIXES:
一个明显的解决方法是使用bindParm()
指定数据类型。有关MySQLi等效项,请参阅mysqli_stmt_bind_param()
:
$stmt->bindParam(1, $array[0], PDO::PARAM_INT);
如果由于某种原因,更改程序太麻烦,那么间接修复将在查询中使用CAST
。例如:
ROUND((ROUND((7002) / CAST(? AS DECIMAL(10,2))) * CAST(? AS DECIMAL(10,2))) / 3600,2)
可以在param_rounded_modified
中看到这样的结果,尽管这会以增加查询的一小部分复杂性为代价。
总之,我觉得,结论会有点扭曲。错误更多的是流行惯例(没有指定数据类型的绑定),因为这样的情况会暴露出来,并不总是正确的。我们显然无法将其称为错误(尽管存在如上所述的异常),因为MYSQL确实提供了在参数化查询中指定数据类型的方法,即使它通常没有明确要求(在PDO中) 相当常用的算术运算。