[PHP-ZF] Zend_Db_Table_Select 兩層 SubQuery

分類: 技術分享 作者: jaceju

25 4 月 2009

Zend_Db_Table_Select 簡介

SQL 語法的組成,一直以來都是 PHP 開發者很難掌控的部份;相信大家應該都經歷過 PHP 執行 SQL 語法時,老是丟出語法錯誤的訊息,尤其在我們透過判斷去組合 SQL 條件式的時候。

$sql  = "SELECT * FROM table";
$sql .= " WHERE field1 = '$field1'";
if ('' !== $field2) {
    $sql .= " AND field2 = '$field2'";
}
$dataList = $db->fetchAll($sql);

Zend Framework 的 Zend_Db_Select 就是用來解決這種問題的。這兩個元件可以讓我們用物件導向的語法去產生 SQL 敘述,這樣我們就不必擔心在組合 SQL 敘述時,可能會漏掉某個 "AND" 或是少了 個空白等簡單的錯誤;而且它還可以封裝各家 SQL 引擎在語法解釋上的不同 (但不是百分之百) ,讓我們用更抽象的方式來操作資料庫。

// Zend_Db_Select
$select = $db->select()->from('table')
             ->where('field1 = ?', $field1)
if ('' !== $field2) {
    $select->where('field2 = ?', $field2);
}
 
$dataList = $db->fetchAll($select);

這時的 $dataList 就會是我們所需要的資料陣列。

Zend_Db_Table_Select 簡介

雖然 Zend_Db_Select 可以幫我們解決 SQL 語法組合的問題,可惜透過它來取得的資料都是陣列。如果今天我們的商業邏輯是寫在 Row 物件裡,那麼我們其實需要的是 Rowset 集合物件。

在 1.0.x 以前的版本,我們如果要取得 Rowset 集合,最簡單的方法就是透過 Zend_Db_Table_Abstract 的 fetchAll() 方法:

// Zend Framework ver 1.0.4
abstract class Zend_Db_Table_Abstract
{
    // ...
 
    /**
     * Fetches all rows.
     *
     * Honors the Zend_Db_Adapter fetch mode.
     *
     * @param string|array $where            OPTIONAL An SQL WHERE clause.
     * @param string|array $order            OPTIONAL An SQL ORDER clause.
     * @param int          $count            OPTIONAL An SQL LIMIT count.
     * @param int          $offset           OPTIONAL An SQL LIMIT offset.
     * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
     */
    public function fetchAll($where = null, $order = null, $count = null, $offset = null) {}
 
    // ...
}

但是你也看到了, fetchAll() 方法所提供的參數其實有點簡陋;如果我們的所需要的 SQL 語法較為複雜時, fetchAll() 就力有未逮了;所以在 Zend Framework 1.5 版時,就引入了 Zend_Db_Table_Select 。

在 1.5 版以後的 Zend_Db_Table_Abstract::fetchAll() 方法,其第一個參數可以接受一個 Zend_Db_Table_Select 物件 (但還是有向下相容) ,用法如下:

$table1 = new Table1(); // Table1 extend Zend_Db_Table
$select = $table1->select()->limit(20);
$rowset = $table1->fetchAll($select);

不過 Zend_Db_Table_Select 為了保持 Table Model 所 fetch 出來的 Row 物件裡欄位的純正性,預設並不讓我們 join 其他資料表;這時我們可以透過它的 setIntegrityCheck() 方法,讓 fetch 出來的 Row 物件可以混雜其他資料表的欄位。

$table1 = new Table1(); // Table1 extend Zend_Db_Table
$select = $table1->select()
                 ->setIntegrityCheck(false)
                 ->join('table2', 'table1.table2Id = table2.id', array('field3', 'field4'))
                 ->limit(20);
$rowset = $table1->fetchAll($select);

另外 Zend_Db_Table_Select 和 Zend_Db_Select 還有一個不同點,就是 Zend_Db_Table_Select 預設 from() 所指向的資料表,就是產生它的資料表物件 (在上例就是 $table1 ) 。如果你希望更改 from() 預設的資料表,那麼也是要呼叫 setIntegrityCheck(false) 才能運作,不然 Zend_Db_Table_Select 會送你一個錯誤訊息。

Zend_Db_Table_Select 的兩層 SubQuery 寫法

因為專案的需求,同事需要一個較為複雜的 SQL 語法;在我跟他討論之後,就決定用兩層的 SubQuery 來組合出這個 SQL 。

本來我想說直接使用純字串的 SQL 語法來取得資料即可,但後來同事提到他會用到 Row 物件裡的方法。因此,我就改用 Zend_Db_Table_Select 來組出我們所需要的 Select 物件:

$table1 = new Table1(); // Table1 extend Zend_Db_Table
$selectX = $table1->select()->order('xxx')->limit(20);
$selectY = $table1->select()->setIntegrityCheck(false)
                  ->from(array('Y' => new Zend_Db_Expr('(' . $selectX . ')')))
                  ->order('RAND()');
$selectZ = $table1->select()->setIntegrityCheck(false)
                  ->from(array('Z' => new Zend_Db_Expr('(' . $selectY . ')')))
                  ->limit(3);
$rowset = $table1->fetchAll($selectZ);

註:這裡的 Zend_Db_Expr 是告訴 Zend_Db_Table_Select 直接把它的引數當成純字串看待,不要做任何處理。

希望有了前面講的觀念,這段程式就不會讓大家有所迷惑了。 (謎:你這梗也鋪得太長了吧 XD)

謝謝收看~

我要留言

關於這裡

這個部落格分享了哇寶在電子商務領域的技術及資訊,希望能讓更多人一起為台灣的網路產業加油。