where 相關的子句構造完成後,咱們繼續構造其它子句。這一篇咱們進行聚合函數、分組、排序等子句的構造。php
在 SQL 中,有一些用來統計、彙總的函數,被稱做聚合函數,如 SUM、COUNT、AVG 等。git
使用 select() 方法時,咱們能夠用 select('COUNT(id)') 這種寫法來使用聚合函數,可是這種方式有缺點:github
爲了更方便的得到聚合數據,咱們須要爲其單獨編寫方法。sql
得到某一列的方法能夠由 PDO::FETCH_COLUMN 來完成。函數
基類添加 getList() 方法:測試
public function getList($field) { $this->_cols_str = ' '.self::_quote($field).' '; $this->_buildQuery(); $this->_execute(); // 獲取一列數據 return $this->_pdoSt->fetchAll(PDO::FETCH_COLUMN, 0); }
基類添加 count() 方法:fetch
public function count($field = '*') { // 判斷是否時 * // 非 * 時給字段添加引號 if(trim($field) != '*') { $field = self::_quote($field); } // 構造列查詢字符串 $this->_cols_str = ' COUNT('.$field.') AS count_num '; // 取結果 return $this->row()['count_num']; }
構造 SQL SELECT COUNT(id) FROM test_table;
ui
$results = $driver->table('test_table') ->count('id');
因爲聚合函數方法和 get()、row() 方法同樣是取結果的,鏈式調用時切記要放到最後執行。this
sum()、avg() 等方法沒有 COUNT('*') 這樣的場景,比 count() 方法的編寫更簡單。code
sum() 方法:
public function sum($field) { $this->_cols_str = ' SUM('.self::_quote($field).') AS sum_num '; return $this->row()['sum_num']; }
其它方法如 avg()、max() 之類的編寫就不一一展現了,代碼請看 WorkerF - 聚合函數 。
group by 子句指定了要分組的字段,能夠是一個或多個 (用逗號隔開)。
having 子句和 group by 子句一塊兒使用,做爲分組的篩選條件,能夠爲空,除了關鍵字外,語法和 where 子句基本相同。
基類新增 groupBy() 方法:
public function groupBy($field) { // 是否初次調用 ? if($this->_groupby_str == '') { $this->_groupby_str = ' GROUP BY '.self::_wrapRow($field); } else { // 非初次調用(多個分組字段),使用逗號分隔 $this->_groupby_str .= ' , '.self::_wrapRow($field); } return $this; }
基類新增 having()、orHaving() 方法:
public function having() { $operator = 'AND'; // 初次調用 ? if($this->_having_str == '') { $this->_having_str = ' HAVING '; } else { $this->_having_str .= ' '.$operator.' '; } // 和 where 子句同樣進行條件構造 $this->_condition_constructor(func_num_args(), func_get_args(), $this->_having_str); return $this; } public function orHaving() { $operator = 'OR'; if($this->_having_str == '') { $this->_having_str = ' HAVING '; } else { $this->_having_str .= ' '.$operator.' '; } $this->_condition_constructor(func_num_args(), func_get_args(), $this->_having_str); return $this; }
這裏咱們也留一個處理原生字符串的 havingRaw() 方法 (手動填寫數據,不進行數據綁定):
public function havingRaw($string) { $this->_having_str = ' HAVING '.$string.' '; return $this; }
構造 SQL SELECT id, SUM(price) FROM test_table GROUP BY price HAVING SUM(price) > 1000;
:
$results = $driver->table('test_table') ->select('id', 'SUM(price)') ->groupBy('price') ->having('SUM(price)', '>', 1000) ->get(); // 使用 havingRaw() $results = $driver->table('test_table') ->select('id', 'SUM(price)') ->groupBy('price') ->havingRaw('SUM(price) > 1000') ->get();
排序就很簡單了,固定的語法和正序、倒序兩個模式,多個字段排序時使用逗號隔開:
public function orderBy($field, $mode = 'ASC') { $mode = strtoupper($mode); if ( ! in_array($mode, ['ASC', 'DESC'])) { throw new \InvalidArgumentException("Error order by mode"); } // 初次調用? if($this->_orderby_str == '') { $this->_orderby_str = ' ORDER BY '.self::_wrapRow($field).' '.$mode; } else { // 多個排序字段時,逗號隔開 $this->_orderby_str .= ' , '.self::_wrapRow($field).' '.$mode; } return $this; }
構造 SQL SELECT * FROM tes_table ORDER BY price DESC, id ASC;
:
$results = $driver->table('test_table') ->orderBy('price', 'DESC') ->orderBy('id', 'ASC') ->get();
標準 SQL 中的 limit 子句是 limit、offset 關鍵字一塊兒使用的,Mysql 中有 LIMIT 0, 10
的簡寫形式,可是 PostgreSql 和 Sqlite 並不適用。因此咱們選用 limit、offset 語法:
public function limit($offset, $step) { $this->_limit_str = ' LIMIT '.$step.' OFFSET '.$offset.' '; return $this; }
構造 SQL SELECT * FROM test_table LIMIT 10 OFFSET 1;
:
$results = $driver->table('test_table') ->limit(1, 10) ->get();
在數據請求時會遇到請求資源數據量大的問題,對於這個問題,廣泛的解決方案就是分頁。
有了 limit() 方法,能夠進行分頁功能的實現了。
爲了靈活的訪問分頁,咱們要返回如下的數據:
對於獲取數據的總數,這裏有一些問題。
如何獲取總數?固然是使用上面講到的聚合函數 count() 來處理。可是,使用了 count() 方法後至關於進行了一次 SQL 的構造和執行 (執行後會將構造字符串設置爲初始狀態),那麼還如何進行當頁數據集合的獲取呢?
兩次執行
固然是有解決方案的,就是構造兩次。
咱們的查詢構造器結構每次新建一個實例就會獲得一個 PDO 的鏈接,因此爲了構造兩次而新建實例的話,代價太大,那麼如何在一個實例中實現兩次執行?
回顧上一篇,含有子查詢的 SQL 構造時通過了兩次構造,對構造字符串進行了保護、恢復。那麼換到分頁中,仍是構造兩次 SQL,對構造字符串進行保護、恢復,區別就是:由於要執行兩次,因此要對綁定的數據也進行保護、恢復。
基類中添加綁定數據保存、恢復的方法:
// 保存綁定數據 protected function _storeBindParam() { return $this->_bind_params; } // 恢復綁定數據 protected function _reStoreBindParam($bind_params) { $this->_bind_params = $bind_params; }
基類中添加 paginate() 方法:
public function paginate($step, $page = NULL) { // 保存構造字符串和綁定數據 $store = $this->_storeBuildAttr(); $bind_params = $this->_storeBindParam(); // 獲取數據總數 $count = $this->count(); // 恢復構造字符串和綁定數據 $this->_reStoreBuildAttr($store); $this->_reStoreBindParam($bind_params); // 建立分頁數據 $page = $page ? $page : 1; // 第一頁 $this->limit($step * ($page - 1), $step); $rst['total'] = $count; $rst['per_page'] = $step; $rst['current_page'] = $page; $rst['next_page'] = ($page + 1) > ($count / $step) ? NULL : ($page + 1); $rst['prev_page'] = ($page - 1) < 1 ? NULL : ($page - 1); $rst['first_page'] = 1; $rst['last_page'] = $count / $step; $rst['data'] = $this->get(); // 返回結果 return $rst; }
測試一下,獲取從第五頁的數據,每頁 10 條數據:
$results = $driver->table('test_table') ->paginate(10, 5);
Just do it!