起因
使用了某個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!