Bonjour à tous !

Voilà je suis face à la construction d'une association assez complexe (du moins pour moi pauvre débutant CakePHP qui réalise son 1er projet avec ce fabuleux framework).

J'ai 4 tables :
users (utilisateurs) : id, username, profil_id
profils (profile) : id, user_id, city_id
cities (villes) : id, name, zip
visits (visites sur les profils avec id profil du visiteur et id profil du visité) : id, profil_id, visited_id

Le model "Profil" correspondant à la table "profils" est par défaut associé aux modèles City (table "cities") et User (table "users").

Lorsque je récupère la liste des visites, j'associe le model "Visit" de la table "visits", au model "Profil".
L'association fonctionne bien, je récupère bien ma liste d'entrées avec pour chacune le profil associé.

Maintenant je voudrais aussi profiter de l'occasion pour récupérer l'utilisateur associé au profil ainsi que la ville.

Les model "City" et "User" étant par défaut associé à mon model "Profil", je peu récupérer mes infos si je met le "recursive" à 2.

Seulement je constate qu'avec ce procédé CakePHP exécute 5 requêtes SQL au lieu d'1, ce qui du coup retire tout l’intérêt de l'association.

J'aimerais donc savoir comment construire cette requête de la manière la plus optimisée possible.

Merci d'avance pour votre aide

Mickael

7 réponses


Soundboy39
Auteur
Réponse acceptée

Merci à tous pour votre aide !

Bon c'est bon j'ai réussi à régler tous mes problèmes et à diviser le nombre de requêtes par 2 en ne faisant qu'un seul "find()" pour toutes les entrées.

Une fois que j'ai récupéré la liste des "profil_id" de chaque annonce je fais un "find all" avec la liste des Id en paramètre de conditions pour le champs "Profil.id". Voilà comment il faut procéder : http://stackoverflow.com/questions/9445086/how-using-sql-in-operator-in-find-method-of-cakephp-orm
Au final çà donne une requête avec une condition de type "IN", chose que je n'avais pas encore réussi à faire avec CakePHP.

En moyenne j'ai gagné plus de 500ms sur l’exécution total de mon script, de quoi bien commencé la semaine ! :-)

5 requêtes ? Pourrais-tu nous indiquer quelles sont les requêtes réalisées, parce qu'en l'état, j'en compte 3/4 tout au plus.

A mon sens, les requêtes supplémentaires sont générés par le recursive à 2. Je t'invite à te tourner vers le behavior Containable à place, qui te permettra de spécifier lors de ton find les différents modèles liés que tu veux associer.

Tu devrais donc avoir quelque chose du genre :

$this->Visit->find('first', 'contain' => array('Profil', 'Profil.City', 'Profil.User'));

Cela dit, il est logique d'avoir plusieurs requêtes, surtout si tu fais un find('all'). Sur un find first, il me semble que les requêtes s'imbriquent pour n'en former qu'une seule par contre.

Par contre, quand tu dis qu'avoir plusieurs requêtes retire l'intérêt de l'association, je t'arrête tout de suite, ça n'a rien à voir.

Oui j'ai du mal comprendre... parce que je souhaite vraiment limiter le nombre de requêtes... c'est pour çà que j'aurais voulus une seul requête avec une jointure.

Avoir tes requêtes nous aiderait à comprendre ce qu'il se passe. Éventuellement le code de tes modèles aussi (enfin uniquement les liaisons), et si possible le find que tu réalises dans ton controller Visits.

Ensuite, il faut savoir que CakePHP fonctionne comme cela pour les queries (en tout cas en version 2.x) :

  • une requête unique avec des JOIN (le type de join, left ou inner, peut être défini dans les liaisons) quand il s'agit d'une relation hasOne ou belongsTo
  • plusieurs SELECT successifs dans le cas de toute autre relation (hasMany, hasAndBelongsToMany)

En faisant quelques recherches, j'ai trouvé ce tuto (en anglais) pour forcer les SQL Join même sur les relations pour lesquelles Cake ne les utilise pas : http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/

Après, cela revient à chaque fois à délier les modèles et les réassembler lors de la requête. On perd tout l'intérêt des liaisons de modèles de Cake.

Je pense aussi que ta méconnaissance des bases de données te met sur une fausse piste : n'avoir qu'une seule requête n'est pas forcément synonyme de performances et de temps de réponses. Ce qui va déterminer tout cela, c'est d'une part la structure de tes tables, la façon dont tu spécifies de façon précise les index, la configuration de la BDD, la taille de tes tables également, et d'autre part le façon dont tu gères ton cache application.

Cake se base sur des standards, et dans le cas d'un hasMany et encore plus dans celui d'un hasAndBelongsToMany (avec les tables de jointures) il s'avère que les perfs sont meilleures en enchaînant plusieurs SELECT plutôt qu'en réalisant des JOIN.

Pour conclure, je dirais que selon ta config serveur, tu as un cap à partir duquel le JOIN retrouve son utilité à haut volume (5, 10, 15...), et pour déterminer cela, il n'y a que les tests qui pourront t'aider. Mais d'un côté, quand tu en arrives à 15 niveau de récursivité sur un find, le problème se situe plus sur l'organisation des données et sur la pertinence d'une telle requête qu'au niveau de ce que fait Cake ou non.

OK Merci pour toutes ces précisions, voilà une réponse plus que complète...

Par contre
"une requête unique avec des JOIN (le type de join, left ou inner, peut être défini dans les liaisons) quand il s'agit d'une relation hasOne ou belongsTo"...
Je suis un peu étonné car mes associations sont de type "belongsTo", mais j'ai quand même plusieurs requêtes.

Je vais essayer la solution proposée par ton lien, je te fais un retour dès que possible.

re,

Finalement j'opte pour une solution plus propre et plus logique, je récupère d'abord la liste des visites puis pour chaque visite je fais une requête "find()" avec le modèle "Profil".

Par contre ensuite j'ai ajouté 2 nouvelles tables qui sont associé au profil : "images" et "autorisations".

Le problème que j'ai c'est que je peux associé la table "images" par défaut dans la propriété "hasMany" du modèle "Profil", mais pas la table "autorisation" dont un des critères "conditions" dépend d'une variable définis dans mon script.

Du coup je fais une association à la volée :

$this->Visit->Profil->Profil->bindModel(array('hasMany' => array('Autorisation')));

Alors vous allez rigoler... l'association n'est prise en compte que sur mon 1er "find()", le modèle est bien associé au Profil, mais pas sur les autres "find()" qui suivent.. Mais pourquoi donc ?!

Je précise que je fais mon association avant la boucle.
Si je regarde les requêtes affichées via l’élément "sql_dump" je constate bien que le SELECT est fait pour le premier profil mais pas pour les autres.

Finalement j'opte pour une solution plus propre et plus logique, je récupère d'abord la liste des visites puis pour chaque visite je fais une requête "find()" avec le modèle "Profil".

Coucou, je me suis juste arréter sur ca
En gros si tu as 70 visites tu as 70 find dérrière ... c'est pas plus propre et plus logique du coup
Vaut mieux dans certains cas plusieurs requetes simple que des requêtes complexe mais quand une jointure doit etre fait il faut la faire
Niveau perf c'est vraiment pas optimal de faire des requêtes dans une boucle dont tu connais pas ou dont tu ne peut pas maitrisser la taille

Après pour le reste je pense que Pakito à déjà tout dis ;)