起因
使用了某个 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!
来源: https://segmentfault.com/a/1190000040722452