このような状況では、Cakeのアソシエーション、つまりContainableを使用せず、自分で結合を作成する傾向があります。
$events = $this->Event->find('all', array(
'joins'=>array(
array(
'table' => $this->Schedule->table,
'alias' => 'Schedule',
'type' => 'INNER',
'foreignKey' => false,
'conditions'=> array(
'Schedule.event_id = Event.id',
),
),
array(
'table' => $this->Date->table,
'alias' => 'Date',
'type' => 'INNER',
'foreignKey' => false,
'conditions'=> array(
'Date.schedule_id = Schedule.id',
),
),
),
'conditions'=>array(
'Date.start >=' => $start_date,
'Date.start <=' => $end_date,
),
'order'=>'Event.created DESC',
'limit'=>5
));
少し分厚いですが、私が望む正確なクエリになります。
更新
コードを部分的に分割して、どこで改善できるか見てみましょう。最初の部分は、find
の準備です。 。コードを短くしようと書き直しました。これが私が思いついたものです:
// Default options go here
$defaultOpts = array(
'start' => date('Y-m-d') . ' 00:00:00',
'end' => date('Y-m-d') . ' 23:59:59',
'limit' => 10
)
// Use default options if nothing is passed, otherwise merge passed options with defaults
$opts = is_array($opts) ? array_merge($defaultOpts, $opts) : $defaultOpts;
// Initialize array to hold query conditions
$conditions = array();
//date conditions
$conditions[] = array(
"Date.start >=" => $qOpts['start'],
"Date.start <=" => $qOpts['end'],
));
//cities conditions
if(isset($opts['cities']) && is_array($opts['cities'])) {
$conditions['OR'] = array();
$conditions['OR'][] = array('Venue.city_id'=>$opts['cities']);
$conditions['OR'][] = array('Restaurant.city_id'=>$opts['cities']);
}
//event types conditions
//$opts['event_types'] = array('1');
if(isset($opts['event_types']) && is_array($opts['event_types'])) {
$conditions[] = 'EventTypesEvents.event_type_id' => $opts['event_types']
}
//event sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_types'])) {
$conditions[] = 'EventSubTypesEvents.event_sub_type_id' => $opts['event_sub_types']
}
//event sub sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_sub_types'])) {
$conditions[] = 'EventSubSubTypesEvents.event_sub_sub_type_id' => $opts['event_sub_sub_types']
}
ほとんどのORを削除したことに注意してください。これは、conditions
の値として配列を渡すことができるためです。 、CakeはそれをIN(...)
にします SQLクエリのステートメント。例:'Model.field' => array(1,2,3)
'Model.field IN (1,2,3)'
を生成します 。これはORと同じように機能しますが、必要なコードは少なくなります。したがって、上記のコードブロックは、コードが実行していたのとまったく同じように動作しますが、より短くなります。
次に、複雑な部分であるfind
が登場します。
通常は、Containableを使用せず、'recursive'=>false
を使用して、強制結合のみを使用することをお勧めします。 。これは通常だと思います 複雑な検索に対処するための最良の方法です。 Associations and Containableを使用すると、Cakeはデータベースに対して複数のSQLクエリ(モデル/テーブルごとに1つのクエリ)を実行しますが、これは非効率的である傾向があります。また、Containableは常に期待される結果を返すとは限りません(試してみたときに気づいたように)。
しかし あなたの場合は4つあるので 複雑な関連付けが含まれている場合は、混合アプローチが理想的なソリューションになる可能性があります。そうでない場合は、複雑すぎて重複データをクリーンアップできません。 (4つの複雑な関連付けは次のとおりです。イベントhasMany Dates [イベントhasManyスケジュール、スケジュールhasMany Dateを介して]、イベントHABTM EventType、イベントHABTM EventSubType、イベントHABTM EventSubSubType)。したがって、CakeにEventType、EventSubType、およびEventSubSubTypeのデータ取得を処理させ、重複が多すぎないようにすることができます。
だからここに私が提案するものがあります:必要なすべてのフィルタリングに結合を使用しますが、フィールドに日付と[Sub[Sub]]タイプを含めないでください。モデルが関連付けられているため、CakeはDBに対して追加のクエリを自動的に実行して、これらのデータをフェッチします。封じ込めは必要ありません。
コード:
// We already fetch the data from these 2 models through
// joins + fields, so we can unbind them for the next find,
// avoiding extra unnecessary queries.
$this->unbindModel(array('belongsTo'=>array('Restaurant', 'Venue'));
$data = $this->find('all', array(
// The other fields required will be added by Cake later
'fields' => "
Event.*,
Restaurant.id, Restaurant.name, Restaurant.slug, Restaurant.address, Restaurant.GPS_Lon, Restaurant.GPS_Lat, Restaurant.city_id,
Venue.id, Venue.name, Venue.slug, Venue.address, Venue.GPS_Lon, Venue.GPS_Lat, Venue.city_id,
City.id, City.name, City.url_name
",
'joins' => array(
array(
'table' => $this->Schedule->table,
'alias' => 'Schedule',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Schedule.event_id = Event.id',
),
array(
'table' => $this->Schedule->Date->table,
'alias' => 'Date',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Date.schedule_id = Schedule.id',
),
array(
'table' => $this->EventTypesEvent->table,
'alias' => 'EventTypesEvents',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'EventTypesEvents.event_id = Event.id',
),
array(
'table' => $this->EventSubSubTypesEvent->table,
'alias' => 'EventSubSubTypesEvents',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'EventSubSubTypesEvents.event_id = Event.id',
),
array(
'table' => $this->Restaurant->table,
'alias' => 'Restaurant',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Event.restaurant_id = Restaurant.id',
),
array(
'table' => $this->City->table,
'alias' => 'RestaurantCity',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Restaurant.city_id = city.id',
),
array(
'table' => $this->Venue->table,
'alias' => 'Venue',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Event.venue_id = Venue.id',
),
array(
'table' => $this->City->table,
'alias' => 'VenueCity',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Venue.city_id = city.id',
),
),
'conditions' => $conditions,
'limit' => $opts['limit'],
'recursive' => 2
));
contains
を削除しました 、およびそのためにCakeが実行していた追加のクエリの一部。ほとんどの結合はタイプINNER
です。 。これは、結合に関係する両方のテーブルに少なくとも1つのレコードが存在する必要があることを意味します。存在しないと、期待よりも少ない結果が得られます。各イベントはレストランで行われると思いますまたは 会場ですが、両方ではありません。そのため、LEFT
を使用しました。 それらのテーブル(および都市)用。結合で使用されるフィールドの一部がオプションの場合は、LEFT
を使用する必要があります INNER
の代わりに 関連する結合について。
'recursive'=>false
を使用した場合 ここでは、適切なイベントが取得され、データの繰り返しはありませんが、日付と[Sub[Sub]]タイプが欠落しています。 2レベルの再帰により、Cakeは返されたイベントを自動的にループし、イベントごとに、関連するモデルデータをフェッチするために必要なクエリを実行します。
これはほとんどあなたがやっていたことですが、Containableがなく、いくつかの追加の調整があります。まだ長くて醜くて退屈なコードであることは知っていますが、結局のところ、13のデータベーステーブルが関係しています...
これはすべてテストされていないコードですが、機能するはずです。