W takiej sytuacji zwykle nie używam skojarzeń Cake'a ani Containable i samodzielnie tworzę połączenia:
$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
));
Jest trochę masywny, ale daje dokładnie takie zapytanie, jakie chcę.
AKTUALIZUJ
Podzielmy Twój kod na części i zobaczmy, gdzie możemy go ulepszyć. Pierwsza część to przygotowanie do find
. Przepisałem Twój kod, próbując go skrócić, i oto, co wymyśliłem:
// 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']
}
Zauważ, że wyeliminowałem większość OR. Dzieje się tak, ponieważ możesz przekazać tablicę jako wartość w conditions
, a Cake zmieni go w IN(...)
instrukcja w zapytaniu SQL. Na przykład:'Model.field' => array(1,2,3)
generuje 'Model.field IN (1,2,3)'
. Działa to podobnie jak OR, ale wymaga mniej kodu. Tak więc powyższy blok kodu robi dokładnie to samo, co twój kod, ale jest krótszy.
Teraz nadchodzi część złożona, find
się.
Zwykle polecam wymuszone łączenia same, bez Containable i z 'recursive'=>false
. Wierzę w to zazwyczaj to najlepszy sposób radzenia sobie ze złożonymi znaleziskami. Dzięki asocjacjom i możliwościom zawierania Cake uruchamia kilka zapytań SQL względem bazy danych (jedno zapytanie na model/tabelę), co zwykle jest nieefektywne. Ponadto Containable nie zawsze zwraca oczekiwane wyniki (jak zauważyłeś, gdy próbowałeś).
Ale ponieważ w twoim przypadku są cztery złożonych skojarzeń, być może podejście mieszane będzie idealnym rozwiązaniem - w przeciwnym razie usunięcie zduplikowanych danych byłoby zbyt skomplikowane. (Cztery złożone skojarzenia to:Zdarzenie ma wiele dat [poprzez Zdarzenie ma wiele harmonogramów, Harmonogram ma wiele dat], Zdarzenie HABTM EventType, Zdarzenie HABTM EventSubType, Zdarzenie HABTM EventSubSubType). Tak więc, moglibyśmy pozwolić Cake'owi zająć się pobieraniem danych EventType, EventSubType i EventSubSubType, unikając zbyt wielu duplikatów.
Oto, co sugeruję:używaj złączeń do wszystkich wymaganych filtrów, ale nie uwzględniaj w polach Date i [Sub[Sub]]Types. Ze względu na powiązania modeli, które masz, Cake automatycznie uruchomi dodatkowe zapytania względem bazy danych, aby pobrać te bity danych. Nie potrzeba żadnych materiałów do przechowywania.
Kod:
// 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
));
Wyeliminowaliśmy contains
i niektóre dodatkowe zapytania, które Cake uruchomił z tego powodu. Większość złączeń jest typu INNER
. Oznacza to, że w obu tabelach uczestniczących w łączeniu musi istnieć co najmniej jeden rekord, w przeciwnym razie uzyskasz mniej wyników niż oczekiwałeś. Zakładam, że każde wydarzenie odbywa się w restauracji LUB miejsce, ale nie jedno i drugie, dlatego użyłem LEFT
dla tych tabel (i miast). Jeśli niektóre pola używane w złączeniach są opcjonalne, powinieneś użyć LEFT
zamiast INNER
na powiązanych sprzężeniach.
Jeśli użyliśmy 'recursive'=>false
tutaj nadal otrzymalibyśmy właściwe zdarzenia i nie powtórzylibyśmy się danych, ale brakowałoby dat i [Sub[Sub]]typów. Dzięki dwóm poziomom rekurencji Cake automatycznie przejdzie przez zwrócone zdarzenia, a dla każdego zdarzenia uruchomi niezbędne zapytania, aby pobrać powiązane dane modelu.
To prawie to, co robiłeś, ale bez Containable i z kilkoma dodatkowymi poprawkami. Wiem, że to wciąż długi, brzydki i nudny fragment kodu, ale w końcu zaangażowanych jest 13 tabel bazy danych...
To wszystko jest nieprzetestowany kod, ale uważam, że powinien działać.