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本体でも実装してくれることに期待します。

2 thoughts to “Zend_Db_SelectでUNIONとHAVINGを同時に使えない”

  1. select …… from …. where ….
    union
    select …… from …. where ….
    having …..

    RDBMSの実装にもよるのかもしれませんが、MySQLだとこのSQLは$select2に対するhavingと解釈されますね。
    なので、同じ結果を得るにはこんな感じになるんじゃないかと。

    $select1 = $adapter->select()->from(….)->where(….);
    $select2 = $adapter->select()->from(….)->where(….)->having(…..);

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

    逆に、FULL JOINをエミュレートしたいのだとすると、
    同じ条件でのLEFT JOINとRIGHT JOINをUNIONするか
    UNIONしてからサブクエリを使うか、どちらかだろうと思いますがいかがでしょう。

  2. >通りすがりさん

    コメントありがとうございます。
    UNIONした結果にHAVINGしているつもりでしたが、通りすがりさんのおっしゃる通り、$select2にだけHAVINGでフィルターされるデータが入っていて、意図通りのデータが出力されていたようでした。

    UNIONの結果をさらに絞り込むときは、書いていただいたようにサブクエリを使うやり方が適切だと思います。

    ありがとうございました。勘違いしたままでいるところでした。

Comments are closed.