關聯查詢是關係型數據庫典型的查詢語句,根據兩個或多個表中的列之間的關係,從這些表中查詢數據。在 SQL 標準中使用 JOIN 和 ON 關鍵字來實現關聯查詢。php
join 子句的構造並不難,注意事項就是關聯查詢的注意事項:mysql
基類中新建 join() 方法:git
// $table 要關聯的表 // $one 做爲關聯條件的一個表的字段 // $two 做爲關聯條件的另外一個表的字段 // $type 關聯模式 inner、left、right public function join($table, $one, $two, $type = 'INNER') { // 判斷模式是否合法 if( ! in_array($type, ['INNER', 'LEFT', 'RIGHT'])) { throw new \InvalidArgumentException("Error join mode"); } // 構建 join 子句字符串 $this->_join_str .= ' '.$type.' JOIN '.self::_wrapRow($table). ' ON '.self::_wrapRow($one).' = '.self::_wrapRow($two); return $this; }
leftJoin() 和 rightJoin() 方法:github
public function leftJoin($table, $one, $two) { return $this->join($table, $one, $two, 'LEFT'); } public function rightJoin($table, $one, $two) { return $this->join($table, $one, $two, 'RIGHT'); }
注:Sqlite 是不支持 right join 的,因此 rightJoin() 方法在 Sqlite 驅動類中無效。
構建 SELECT student.name, class.name FROM student INNER JOIN class ON student.class_id = class.id;
:正則表達式
$results = $driver->table('student') ->select('student.name', 'class.name') ->join('class', 'student.class_id', 'class.id') ->get();
之前不少數據表放在一個數據庫中的時候,須要表前綴來區分功能。雖然如今這樣的狀況已經不多,可是對於查詢構造器而言,仍是要提供一個方便的方法來對錶前綴進行設置,特別是當你沒有權限修改表名的時候。sql
對於有表前綴的表,咱們並不想每次都寫一個前綴,這樣會致使前綴更改後,應用層要跟着修改。因此咱們將表前綴做爲一個配置參數傳入查詢構造器,在查詢構造器的底層進行自動前綴添加。數據庫
表前綴的配置,假設表前綴爲 'test_' :socket
// 以 mysql 爲例 $config = [ 'host' => 'localhost', 'port' => '3306', 'user' => 'username', 'password' => 'password', 'dbname' => 'dbname', 'charset' => 'utf8', 'prefix' => 'test_', 'timezone' => '+8:00', 'collection' => 'utf8_general_ci', 'strict' => false, // 'unix_socket' => '/var/run/mysqld/mysqld.sock', ]; $db = new Mysql($config);
進行自動添加前綴的方法:函數
protected function _wrapTable($table) { // 構造函數傳入的配置中有前綴參數嗎? $prefix = array_key_exists('prefix', $this->_config) ? $this->_config['prefix'] : ''; // 拼接前綴 return $prefix.$table; }
修改 table() 方法:單元測試
public function table($table) { // 自動添加前綴 $this->_table = self::_wrapRow($this->_wrapTable($table)); return $this; }
join 子句中也涉及到表,因此修改 join() 方法:
public function join($table, $one, $two, $type = 'INNER') { if( ! in_array($type, ['INNER', 'LEFT', 'RIGHT'])) { throw new \InvalidArgumentException("Error join mode"); } // 添加表前綴 $table = $this->_wrapTable($table); $this->_join_str .= ' '.$type.' JOIN '.self::_wrapRow($table). ' ON '.self::_wrapRow($one).' = '.self::_wrapRow($two); return $this; }
增長了表前綴後,咱們會發現一個問題:
使用 table()、join() 方法傳入的表能夠自動的添加前綴,可是 table.field 格式中的表無法自動添加前綴,如上面的 join('class', 'student.class_id', 'class.id')
,咱們總不能每次都寫成 join('class', 'test_student.class_id', 'test_class.id')
這種 (這樣的話和所有手工添加前綴沒什麼兩樣),必須找到一個自動添加前綴的辦法。
觀察 table.field 模式,它出現的位置不定,可能在列、任何一個子句中出現,因此在固定的位置去添加前綴是不大可能的。那麼咱們反過來想一下,若是在 SQL 已經構造完成但還未執行時,這時已經知道有哪些地方使用了這種格式,去一一替換便可。那麼如何知道有哪些地方使用了這種格式?
使用正則
咱們用正則表達式找到 table.field 的 table 部分,給 table 加上表前綴便可 (這裏不考慮跨庫查詢時三個點的狀況)。
基類新增 _wrapPrepareSql() 方法:
// 替換 table.field 爲 prefixtable.field protected function _wrapPrepareSql() { $quote = static::$_quote_symbol; $prefix_pattern = '/'.$quote.'([a-zA-Z0-9_]+)'.$quote.'(\.)'.$quote.'([a-zA-Z0-9_]+)'.$quote.'/'; $prefix_replace = self::_quote($this->_wrapTable('$1')).'$2'.self::_quote('$3'); $this->_prepare_sql = preg_replace($prefix_pattern, $prefix_replace, $this->_prepare_sql); }
修改 _execute() 方法:
protected function _execute() { try { // table.field 模式添加表前綴 $this->_wrapPrepareSql(); $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_bindParams(); $this->_pdoSt->execute(); $this->_reset(); } catch (PDOException $e) { if($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); try { // table.field 模式添加表前綴 $this->_wrapPrepareSql(); $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_bindParams(); $this->_pdoSt->execute(); $this->_reset(); } catch (PDOException $e) { throw $e; } } else { throw $e; } } }
最後咱們進行一個完整的測試:
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php'; use Drivers\Mysql; $config = [ 'host' => 'localhost', 'port' => '3306', 'user' => 'username', 'password' => 'password', 'dbname' => 'database', 'prefix' => 'test_', 'charset' => 'utf8', 'timezone' => '+8:00', 'collection' => 'utf8_general_ci', 'strict' => false, ]; $driver = new Mysql($config); $results = $driver->table('student') ->select('student.name', 'class.name') ->join('class', 'student.class_id', 'class.id') ->get(); var_dump($results);
試試看吧!
到目前位置,查詢相關的 SQL 構造方法基本開發完畢,咱們進行一些複雜的 SQL 構造吧。
注:這裏只是以個人測試環境舉例,你們能夠按照本身的思路去建表
構造語句 SELECT * FROM t_user WHERE username = 'Jackie aa' OR ( NOT EXISTS ( SELECT * FROM t_user WHERE username = 'Jackie aa' ) AND (username = 'Jackie Conroy' OR username = 'Jammie Haag')) AND g_id IN ( SELECT id FROM t_user_group) ORDER BY id DESC LIMIT 1 OFFSET 0
:
$results = $driver->table('user') ->where('username', 'Jackie aa') ->orWhereBrackets(function($query) { $query->whereNotExists(function($query) { $query->table('user')->where('username', 'Jackie aa'); })->WhereBrackets(function($query) { $query->where('username', 'Jackie Conroy') ->orWhere('username', 'Jammie Haag'); }); }) ->whereInSub('g_id', function($query) { $query->table('user_group')->select('id'); }) ->orderBy('id', 'DESC') ->limit(0, 1) ->get();
構造語句 SELECT t_user.username, t_user_group.groupname FROM t_user LEFT JOIN t_user_group ON t_user.g_id = t_user_group.id WHERE username = 'Jackie aa' OR ( NOT EXISTS ( SELECT * FROM t_user WHERE username = 'Jackie aa' ) AND username = 'Jackie Conroy' )
:
$results = $driver->table('user') ->select('user.username', 'user_group.groupname') ->leftJoin('user_group', 'user.g_id', 'user_group.id') ->where('user.username', 'Jackie aa') ->orWhereBrackets(function($query) { $query->whereNotExists(function($query) { $query->table('user')->where('username', 'Jackie aa'); })->where('user.username', 'Jackie Conroy'); }) ->get();
更多例子參考 WorkerF 單元測試 - PDODQL