ORM:被忽略的 group by 後的 count 統計

Mr_houzi發表於2021-09-26

起因

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

相關文章