В такой ситуации я, как правило, не использую ассоциации 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
));
Это немного коротко, но в результате получается именно тот запрос, который я хочу.
UPDATE
Давайте разберем ваш код по частям и посмотрим, где мы могли бы его улучшить. Первая часть - подготовка к 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']
}
Обратите внимание, что я устранил большинство операций. Это потому, что вы можете передать массив как значение в conditions
, и Cake сделает его оператором IN(...)
в SQL-запросе. Например: 'Model.field' => array(1,2,3)
генерирует 'Model.field IN (1,2,3)'
. Это работает так же, как OR, но требует меньше кода. Таким образом, приведенный выше блок кода делает то же самое, что и ваш код, но он короче.
Теперь идет сложная часть, find
сама.
Обычно я бы рекомендовал принудительные объединения в одиночку, без Containable и с 'recursive'=>false
. Я считаю, что обычно - лучший способ справиться со сложными находками. При использовании ассоциаций и контейнеров Cake выполняет несколько запросов SQL к базе данных (по одному запросу на модель / таблицу), что, как правило, неэффективно. Кроме того, Containable не всегда возвращает ожидаемые результаты (как вы заметили, когда пытались это сделать).
Но , поскольку в вашем случае задействовано четыре сложных ассоциаций, возможно, смешанный подход будет идеальным решением - в противном случае было бы слишком сложно очистить дубликат данные. (4 сложные ассоциации: Событие hasMany Dates [через Событие hasMany Schedule, Расписание hasMany Date], Событие HABTM EventType, Событие HABTM EventSubType, Событие HABTM EventSubSubType). Таким образом, мы могли бы позволить Cake обрабатывать извлечение данных EventType, EventSubType и EventSubSubType, избегая слишком большого количества дубликатов.
Итак, вот что я предлагаю: используйте объединения для всех необходимых фильтров, но не включайте в поля типы Date и [Sub [Sub]]. Из-за имеющихся у вас ассоциаций моделей Cake автоматически выполнит дополнительные запросы к БД, чтобы получить эти биты данных. Не требуется контейнер.
код:
// 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
. Это означает, что по крайней мере одна запись должна существовать в обеих таблицах, участвующих в объединении, иначе вы получите меньше результатов, чем вы ожидаете. Я предполагаю, что каждое Событие происходит в Ресторане ИЛИ Место, но не в обоих, поэтому я использовал LEFT
для этих таблиц (и городов). Если некоторые из полей, используемых в объединениях, являются необязательными, вы должны использовать LEFT
вместо INNER
в связанных объединениях.
Если бы мы использовали 'recursive'=>false
здесь, мы все равно получили бы правильные события, без повторения данных, но даты и типы [Sub [Sub]] были бы пропущены. С двумя уровнями рекурсии Cake будет автоматически перебирать возвращаемые события и для каждого события будет запускать необходимые запросы для извлечения связанных данных модели.
Это почти то, что вы делали, но без Containable и с несколькими дополнительными настройками. Я знаю, что это все еще длинный, уродливый и скучный кусок кода, но в конце концов в нем задействовано 13 таблиц базы данных ...
Это весь непроверенный код, но я считаю, что он должен работать.