CakePHPでLEFT JOIN したテーブルの ON 条件の順番の調整について、foreigenKey が先頭にどうしても来てしまう。今ひとつ調べきれていませんが、ひとまずメモ。ちなみにDBはMySQLです。

(追記:以下の記事を書いてから思いついたのですが、こういう時のアソシエーションは belongsTo ではなくで、 hasOne がふさわしいような気がしてきました。今度検証します)

例えば belongsTo でアソシエーションを組んだモデル

public $belongsTo = array(
	'PrimaryChild' => array(
		'foreignKey' => 'parent_id',
	),
);

を取得する場合は、

ON( `parent_id` = $id ) // $belongsTo['PrimaryChild']['foreignKey']

の形式のクエリが発行されてうまく結合してくれます。

ところが今回、外部キー以外のいくつかの条件を付けて belongsTo で組んだ

public $belongsTo = array(
	'PrimaryChild' => array(
		'foreignKey' => 'parent_id',
		'conditions' => array( 'category' => 12, 'status' => 'primary' ),
	),
);

のようなデータを取得しようとしたところ、この問題にぶち当たりました。

上記の場合、

ON( `parent_id`=$id AND `category`=12 AND `status`='primary' ) // $belongsTo['PrimaryChild']['foreignKey'] が必ず先頭に来る

の形のクエリが作成されます。このうち、2番目と3番目の順番は任意に制御できるのですが、1番目の条件としてModelに設定した条件(=foreignKey)に関しては先頭の位置を変えることは不可能。つまり、

ON( `category`=12 AND `parent_id`=$id AND `status`='primary' ) // $belongsTo['PrimaryChild']['foreignKey'] の位置をコントロールすることはできない

という条件は自動では作成してもらえない、というのが今回の問題です。MySQLのインデックスについての勉強はまだまだ浅いので、根本的に間違っているかもしれませんが、条件の順番をコントロールすることって結構「肝」ではないかと思いますので何とかしたいところですが。。。

ソースを見てもかなり深いところで順番が確定しちゃってて、それを外部からコントロールできる手段も提供されていない。

<?php

class DboSource extends DataSource {

	public function generateAssociationQuery(Model $model, $linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet) {

		// 中略

		switch ($type) {
			case 'hasOne':
			case 'belongsTo':
				$conditions = $this->_mergeConditions(
					$assocData['conditions'],
					$this->getConstraint($type, $model, $linkModel, $association, array_merge($assocData, compact('external', 'self')))
				);

		// 中略

/**
 * Merges a mixed set of string/array conditions
 *
 * @param mixed $query
 * @param mixed $assoc
 * @return array
 */
	protected function _mergeConditions($query, $assoc) {
		if (empty($assoc)) {
			return $query;
		}

		if (is_array($query)) {
			return array_merge((array)$assoc, $query);
		}

		if (!empty($query)) {
			$query = array($query);
			if (is_array($assoc)) {
				$query = array_merge($query, $assoc);
			} else {
				$query[] = $assoc;
			}
			return $query;
		}

		return $assoc;
	}

順番を制御できそうなところは上記の2か所ぐらいと思います。アプリケーション側でこのロジックをオーバーライドってできるのでしょうか?

それは私にはちょっと見つけられないので、どうしてもという場合には仕方ないですが手書きをするしかないのでしょうか。

ひとまず今回はここまで。