ORM:被忽略的 group by 后的 count 统计
起因
使用了某个PHP框架的 sql 查询器时,发现每次加上分组条件之后,page 方法返回参数中一个是数据集和一个是总数,而总数总是不准。
问题
于是,我看了一下框架模型的实现方法。
对于,下面这个 sql 查询器,page() 方法会执行两条 sql ,一条是查数据集,一条是查符合条件的数据集总数,对应的返回 [list, total]。
list($list, $total) = $articleModel
->select([
'XXX',
'XXX',
])
->groupBy('type')
->page($page, $num);
对于总数的不准,问题出现在 sql 的查询
和处理结果
中,对于上面这个业务的查询,会生成:
# sql1
select count(*) from article group by type;
对比 sql1 和下面的 sql2 ,思考一下有什么不同?
# sql2
select count(*) from article;
sql2 是计算的整张表的记录总数,且返回结果永远只有一条数据;
sql1 比 sql2 多加了一个 type 条件的分组,它计算的是分组后的每个分组下的记录总数,即type1的总数,type2的总数……,返回结果是1-N条。
解决
而框架的bug恰在于此,在处理普通查询
和分组查询
时没有单独处理。
框架 count() 中统一拿取结果集的第一值作为总数,当在普通查询中,这是没问题的;但是在分组查询中,拿到的只是type1分组下的总数。分组查询时,我们要拿总是应该时分组的数量。
// 框架中原代码
public function count($column = '*')
{
if (!$this->DB) {
throw new Exception('您还没有连接数据库', Exception::CODE_DATABASE_ERROR);
}
$bak = $this->_sql;
$sql = $this->buildSql($column);
$this->sql = $sql;
if ($this->justSql) {
return 0;
}
$info = $this->DB->query($sql)->fetch(\PDO::FETCH_ASSOC); // 问题在于此,就只拿结果集的第一个记录
$this->lastQueryAt = time();
return isset($info['num']) ? $info['num'] : 0;
}
找到了问题,于是我进行了修改,并给框架提了个 pr。
改后代码如下:
// 改造后代码
public function count($column = '*')
{
if (!$this->DB) {
throw new Exception('您还没有连接数据库', Exception::CODE_DATABASE_ERROR);
}
$bak = $this->_sql;
$sql = $this->buildSql($column);
$this->sql = $sql;
if ($this->justSql) {
return 0;
}
$total = 0;
if (count($bak['group']) > 0) {
$info = $this->DB->query($sql)->fetchAll(\PDO::FETCH_ASSOC); // 有分组时,拿结果集的多个记录
$total = count($info);
} else {
$info = $this->DB->query($sql)->fetch(\PDO::FETCH_ASSOC); // 无分组时,依然如原来一样拿结果集的第一个记录
$total = isset($info['num']) ? $info['num'] : 0;
}
$this->lastQueryAt = time();
return $total;
}
end!