2010-08-18 5 views
1

J'ai été occupé avec le framework cakePHP pendant quelques mois maintenant et je l'aime vraiment. En ce moment je travaille sur un tout nouveau projet et il fait le travail comme il se doit (je pense ...) mais je me sens mal à l'aise avec un code que j'ai écrit. En fait, je devrais optimiser ma requête de conditions de pagination afin d'obtenir immédiatement les bons résultats (maintenant je manipule le jeu de résultats par une série d'appels de méthode Set ::Requête de recherche complexe pour la relation hasMany dans cakePHP 1.3

Je vais esquisser les aspects pertinents de l'application. J'ai un modèle 'Site' qui a une relation hasMany avec le modèle 'SiteMeta' Cette dernière table ressemble à ceci: id, id_site, clé, valeur, créé

Dans ce dernier modèle, j'enregistre plusieurs valeurs du Le nom de la clé que je veux stocker (par exemple, alexarank, google pagerank, ...), et bien sûr aussi la valeur.A un intervalle donné, je laisse mon application mettre à jour cette base de données afin que je puisse suivre l'évolution de ces valeurs

Maintenant, mon problème est le suivant.

Sur la page d'aperçu des différents sites Web (controller => Sites, action => index) Je souhaite afficher le pagerank ACTUEL du site. J'ai donc besoin d'un enregistrement SiteMeta exact où le champ 'créé' est le plus haut et la valeur 'clé' devrait correspondre au mot 'pagerank'. J'ai essayé plusieurs choses que j'ai lues sur le net mais je n'ai obtenu aucun d'entre eux (containable, bindmodel, etc.). Je fais probablement quelque chose de mal.

En ce moment j'obtenir des résultats comme celui-ci quand je fais un $ this-> paginez

Array 
(
    [0] => Array 
     (
      [Site] => Array 
       (
        [id] => 1 
        [parent_id] => 0 
        [title] => test 
        [url] => http://www.test.com 
        [slug] => www_test_com 
        [keywords] => cpc,seo 
        [language_id] => 1 
       ) 
      [SiteMeta] => Array 
       (
        [0] => Array 
         (
          [id] => 1 
          [site_id] => 1 
          [key] => pagerank 
          [value] => 5 
          [created] => 2010-08-03 00:00:00 
         ) 

        [1] => Array 
         (
          [id] => 2 
          [site_id] => 1 
          [key] => pagerank 
          [value] => 2 
          [created] => 2010-08-17 00:00:00 
         ) 

        [2] => Array 
         (
          [id] => 5 
          [site_id] => 1 
          [key] => alexa 
          [value] => 1900000 
          [created] => 2010-08-10 17:39:06 
         ) 
       ) 
     ) 

Pour obtenir le pagerank je viens de boucle à travers tous les sites et de manipuler ce tableau que je reçois. Ensuite, je filtre les résultats avec Set :: extract. Mais cela ne se sent pas tout à fait raison :)

$sitesToCheck = $this->paginate($this->_searchConditions($this->params)); 

foreach($sitesToCheck as $site) { 
      $pagerank = $this->_getPageRank($site['Site']); 
      $alexa = $this->_getAlexa($site['Site']); 
      $site['Site']['pagerank'] = $pagerank; 
      $sites[] = $site; 
     } 

if (isset($this->params['named']['gpr']) && $this->params['named']['gpr']) { 
       $rank = explode('-', $this->params['named']['gpr']); 
         $min = $rank[0];$max = $rank[1]; 
       $sites = Set::extract('/Site[pagerank<=' . $max . '][pagerank>=' . $min .']', $sites); 
     } 

$this->set(compact('sites', 'direction'));   

Pourriez-vous m'aider s'il vous plaît à réfléchir à une solution pour cela? Merci d'avance.


Merci pour les contributions. J'ai essayé ces options (aussi quelque chose avec bindmodel mais ne fonctionne pas aussi) mais je n'arrive toujours pas à faire fonctionner ça comme il se doit. Si je décris cette

$this->paginate = array(
        'joins'=> array(
           array(
            'table'=>'site_metas', 
            'alias'=>'SiteMeta', 
            'type' =>'inner', 
            'conditions' =>array('Site.id = SiteMeta.site_id') 
            )             
         ),                 
     ); 

-je obtenir des résultats en double J'ai un site avec 3 enregistrements SiteMeta différents et un site avec 2 dossier différent. La méthode paginate me renvoie 5 enregistrements au total. Il ya probablement une solution facile pour cela, mais je ne peux pas le comprendre :)

J'ai aussi essayé d'écrire une requête sql moi-même, mais il semble que je ne puisse pas utiliser la magie de la pagination dans ce cas. La requête que j'aimerais imiter avec les options et les conditions de pagination est la suivante. La requête retourne exactement comme je voudrais l'obtenir.

$sites = $this->Site->query('SELECT * FROM sites Site, site_metas SiteMeta WHERE SiteMeta.id = (select SiteMeta.id from site_metas SiteMeta WHERE Site.id = SiteMeta.site_id AND SiteMeta.key = \'pagerank\' order by created desc limit 0,1)'); 
+0

Alors, quel tableau voulez-vous avoir en conséquence au lieu de votre paginate? Ou quelle requête SQL voulez-vous produire? – bancer

+0

merci pour la réponse rapide. Je pensais à un tableau avec un seul élément sur l'index SiteMeta au lieu de 3 dans l'exemple. J'aimerais pouvoir appeler simplement la valeur de cet enregistrement, donc je n'aurais pas à boucler le tableau et à changer le tableau moi-même en ajoutant des indexés comme pagerank ou alexa. Est-ce que cela pourrait être accompli? Une suggestion pour une solution meilleure et plus propre est également ok hehe :) – Laurent

Répondre

2

Lorsque vous essayez d'extraire des données dans une relation hasMany, cakephp ne rejoint pas les tables par défaut. Si vous optez pour joins vous pouvez faire quelque chose comme:

$this->paginate = array(
        'joins'=>array(
           array(
           'table'=>'accounts', 
           'alias'=>'Account', 
           'type' =>'inner', 
           'conditions' =>array('User.id = Account.user_id') 
          ) 
          ), 
          'conditions'=> array('OR' => 
           array(
           'Account.name'=>$this->params['named']['nickname'], 
           'User.id' => 5) 
           ) 
          ); 
$users = $this->paginate(); 
     $this->set('users',$users); 
debug($users); 
$this->render('/users/index'); 

Vous devez adapter ce selon vos besoins bien sûr. More sur les jointures, comme déjà mentionné dans une autre réponse.

Édition 1: C'est parce qu'il vous manque les secondes 'conditions'. Voir mon extrait de code. Les premières 'conditions' indiquent juste où la jointure se produit, alors que les secondes 'conditions' font la sélection réelle.

Éditer 2:Here quelques informations sur comment écrire des conditions afin de sélectionner les données nécessaires. Vous pouvez utiliser la fonction max de votre rdbms dans la colonne created dans votre état affiné.

Édition 3: Contenants et jointures ne doivent pas être utilisés ensemble. Extrait du manuel: L'utilisation de jointures avec le comportement Containable peut entraîner des erreurs SQL (tables dupliquées). Vous devez donc utiliser la méthode joins comme alternative pour Containable si votre objectif principal est d'effectuer des recherches basées sur des données connexes. Containable est le mieux adapté pour restreindre la quantité de données liées apportées par une instruction find. Vous n'avez pas encore essayé mon edit 2, je pense.

Édition 4: Une solution possible pourrait être d'ajouter un champ last_updated à la table Sites. Ce champ peut ensuite être utilisé dans la deuxième condition pour être comparé à la valeur SiteMeta.created.

+0

Salut, je ne pense pas que ce soit le problème. Quand j'ajoute ceci 'conditions' => array ('Site.title' => 'test') J'ai toujours obtenu 3 enregistrements, parce que le site avec le titre 'test' a 3 enregistrements SiteMeta. Je ne comprends pas tout à fait ... – Laurent

+0

Salut Laurent, vous devez affiner les (secondes) conditions, voir modifier 2. btw il semble que vous êtes sur la bonne voie, juste affiner. – benjamin

+0

indice ajouté dans l'édition 2 – benjamin

0

Essayez quelque chose comme ceci:

$this->paginate = array(
     'fields'=>array(
      'Site.*', 
      'SiteMeta.*', 
      'MAX(SiteMeta.created) as last_date' 
     ), 
     'group' => 'SiteMeta.key' 
     'conditions' => array(
      'SiteMeta.key' => 'pagerank' 
     ) 
    ); 
    $data = $this->paginate('Site'); 

Ou ceci:

$conditions = array(
     'recursive' => 1, 
     'fields'=>array(
      'Site.*', 
      'SiteMeta.*', 
      'MAX(SiteMeta.created) as last_date' 
     ), 
     'group' => 'SiteMeta.key' 
     'conditions' => array(
      'SiteMeta.key' => 'pagerank' 
     ) 
    ); 
    $data = $this->Site->find('all', $conditions); 

Si cela ne fonctionne pas et vérifier thisthis. Je suis sûr à 100% qu'il est possible d'obtenir le résultat souhaité avec une seule requête.

0

Essayez quelque chose comme ça (avec jeu maîtrisable sur vos modèles):

$this->Site->recursive = -1; 

$this->paginate = array(
    'conditions' => array(
     'Site.title' => 'title') //or whatever conditions you want... if any 
    'contain' => array(
     'SiteMeta' => array(
      'conditions' => array(
       'SiteMeta.key' => 'pagerank'), 
      'limit' => 1, 
      'order' => 'SiteMeta.created DESC'))); 

-je utiliser maîtrisable tellement que j'ai fait dans mon fichier app_model il applique à tous les modèles:

var $actsAs = array('Containable'); 
0

Beaucoup pense à tous ceux qui ont réussi à m'aider à travers cela :) Je l'ai eu corrigé après tout hehe.

Finalement, cela a été le truc pour moi

$this->paginate = array(
        'joins'=> array(
            array(
             'table'=>'site_metas', 
             'alias'=>'SiteMeta', 
             'type' =>'inner', 
             'conditions' => array('Site.id = SiteMeta.site_id'))                  
           ), 
        'group' => 'Site.id', 
        'contain' => array(
         'SiteMeta' => array(
           'conditions' => array(
            'SiteMeta.key' => 'pagerank'), 
            'limit' => 1, 
            'order' => SiteMeta.created DESC', 
            ))); 

     $sites = $this->paginate(); 
+0

3ème section d'édition ajoutée. – benjamin

+0

Hey Laurent, content que vous l'ayez fait. J'ai vu une solution similaire quelque part ailleurs, où une personne proche des développeurs de base a aidé quelqu'un à assembler un plus grand gâteau-déclaration. La solution, comme les vôtres mixtes et contient, qui semble être déconseillée dans le manuel: http://book.cakephp.org/view/1047/Joining-tables. Cependant, j'ai trouvé le code où un gâteau-contributeur a fait des choses semblables sans avoir de problèmes. bottomline, quand à un moment donné des erreurs SQL se produisent, omettre le comportement containable. La vôtre, – benjamin