Zend_Db_SelectでUNIONとHAVINGを同時に使えない

訂正:2010/04/09

通りすがりさんにコメントを頂き、UNIONで連結した全体にhavingをかけるのはできないということで正しいということが分かりました。

たまたま、$select2の方にだけhavingでフィルターされるデータがあったため、結果的に意図通りのデータが出力されただけで、全体にhavingが掛かっているわけではありませんでした。

サブクエリを使う方法が適切です。

    // $adapterはZend_Db_Adapterのクラス。MySQLを利用。
    $select1 = $adapter->select()->from(....)->where(....);
    $select2 = $adapter->select()->from(....)->where(....);

    $select = $adapter->select()->union(array($select1, $select2));

   echo $adapter->select()->from($select)->where(....); ←havingで書いてた条件

=========

UNIONとHAVINGを同時に使うSQLを組んでいたときに、Zend_Db_Selectから生成されたSQLからHAVING句が抜け落ちていました。

プログラムは以下のようなものです。

    // $adapterはZend_Db_Adapterのクラス。MySQLを利用。
    $select1 = $adapter->select()->from(....)->where(....);
    $select2 = $adapter->select()->from(....)->where(....);

    $select = $adapter->select()->union($select1, $select2)->having(.....);
    echo $select;   ← ここで HAVING句がなくなってる

上記でできるSQLのイメージは、これです。

   select ...... from .... where ....
   union
   select ...... from .... where ....
   having .....

このSQLをDBサーバで実行すると、動くので構文はあってます。

そこで、Zend_Db_Selectクラスのhaving句を出力するところを見たら、こうなってました。(バージョンは10.2)

    /**
     * Render HAVING clause
     *
     * @param string   $sql SQL query
     * @return string
     */
    protected function _renderHaving($sql)
    {
        if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) {
            $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
        }

        Zend_Debug::dump($sql);
        return $sql;
    }

FROM句とHAVING句が両方ある場合に、HAVINGをつけるって処理ですね。確かにこれだと、UNIONと同時に使ったときは、FROM句がないので、HAVING句がつかないのは納得です。

というわけで、以下のように修正して、UNIONとHAVINGが同時に使えることを確認しました。

    /**
     * Render HAVING clause
     *
     * @param string   $sql SQL query
     * @return string
     */
    protected function _renderHaving($sql)
    {
        // FROMかUNIONがあればOKに変更
        if (($this->_parts[self::FROM] || $this->_parts[self::UNION]) && $this->_parts[self::HAVING]) {
            $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
        }

        Zend_Debug::dump($sql);
        return $sql;
    }

そのうちZend本体でも実装してくれることに期待します。

phpで子クラスへ__constructのパラメタを渡す

今までは、配列で渡してたんですが、違和感があるので、どうにか__constructに渡した形と同じように _initにも渡せないかなと考えて、以下のようにしました。

意図通りに動いてくれています。5個以上パラメタがあるときも稀だと思うので、5個までにしています。増えたら増やせばいいかなと。これで違和感なく使えるようになってすっきりです。

abstract class Hoge_Model_Abstract
{
    final public function __construct()
    {
        // ここに共通初期化処理
 
        if (func_num_args() != 0) {
            list($param1, $param2, $param3, $param4, $param5) = func_get_args();
        }
        $this->_init($param1, $param2, $param3, $param4, $param5);
    }

    protected function _init()
    {
        // 初期化処理するときはこちらで
    }
}