Mysql
 sql >> Base de données >  >> RDS >> Mysql

Sélectionnez Tous les événements avec Event->Schedule->Date entre les dates de début et de fin dans CakePHP

Dans ce genre de situation, j'ai tendance à ne pas utiliser les associations de Cake, ou Containable, et à créer les jointures moi-même :

$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
));

C'est un peu volumineux, mais cela donne la requête exacte que je veux.

MISE À JOUR

Décomposons votre code en plusieurs parties et voyons où nous pourrions l'améliorer. La première partie est la préparation de la find . J'ai réécrit votre code en essayant de le raccourcir, et voici ce que j'ai trouvé :

// 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']
}

Remarquez que j'ai éliminé la plupart des OR. C'est parce que vous pouvez passer un tableau en tant que valeur dans conditions , et Cake en fera un IN(...) instruction dans la requête SQL. Par exemple :'Model.field' => array(1,2,3) génère 'Model.field IN (1,2,3)' . Cela fonctionne comme les OR, mais nécessite moins de code. Ainsi, le bloc de code ci-dessus fait exactement la même chose que votre code, mais il est plus court.

Vient maintenant la partie complexe, le find lui-même.

Habituellement, je recommanderais les jointures forcées seules, sans Containable, et avec 'recursive'=>false . Je crois que c'est généralement est la meilleure façon de gérer les découvertes complexes. Avec Associations et Containable, Cake exécute plusieurs requêtes SQL sur la base de données (une requête par modèle/table), ce qui a tendance à être inefficace. De plus, Containable ne renvoie pas toujours les résultats attendus (comme vous l'avez remarqué lorsque vous l'avez essayé).

Mais puisque dans votre cas il y en a quatre associations complexes impliquées, peut-être qu'une approche mixte sera la solution idéale - sinon, il serait trop compliqué de nettoyer les données en double. (Les 4 associations complexes sont :Event hasMany Dates [via Event hasMany Schedule, Schedule hasMany Date], Event HABTM EventType, Event HABTM EventSubType, Event HABTM EventSubSubType). Ainsi, nous pourrions laisser Cake gérer la récupération des données de EventType, EventSubType et EventSubSubType, en évitant trop de doublons.

Voici donc ce que je suggère :utilisez des jointures pour tous les filtrages requis, mais n'incluez pas les types Date et [Sub[Sub]] dans les champs. En raison des associations de modèles que vous avez, Cake exécutera automatiquement des requêtes supplémentaires sur la base de données pour récupérer ces bits de données. Aucun contenu nécessaire.

Le code :

// 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
));

Nous avons éliminé contains , et certaines des requêtes supplémentaires que Cake exécutait à cause de cela. La plupart des jointures sont de type INNER . Cela signifie qu'au moins un enregistrement doit exister sur les deux tables impliquées dans la jointure, sinon vous obtiendrez moins de résultats que prévu. Je suppose que chaque événement a lieu dans un restaurant OU un lieu, mais pas les deux, c'est pourquoi j'ai utilisé LEFT pour ces tables (et villes). Si certains des champs utilisés dans les jointures sont facultatifs, vous devez utiliser LEFT au lieu de INNER sur les jointures associées.

Si nous avons utilisé 'recursive'=>false ici, nous aurions toujours les bons événements, et aucune répétition de données, mais les dates et [Sub[Sub]]Types seraient manquants. Avec les 2 niveaux de récursivité, Cake parcourra automatiquement les événements renvoyés et, pour chaque événement, il exécutera les requêtes nécessaires pour récupérer les données de modèle associées.

C'est presque ce que vous faisiez, mais sans Containable, et avec quelques ajustements supplémentaires. Je sais que c'est toujours un morceau de code long, laid et ennuyeux, mais après tout, il y a 13 tables de base de données impliquées...

Tout cela est du code non testé, mais je pense que cela devrait fonctionner.