IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Symfony PHP Discussion :

QueryBuilder Select Count fonctionne pas


Sujet :

Symfony PHP

  1. #1
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Bonjour,

    J'ai besoin d'aide pour effectuer une requête de type Select COUNT avec le query builder.

    J'ai une table/entité Hotel et une table/entité Chambre

    Pour récupérer et compter le nombre de chambres pour chaque hôtel, je fais la requête suivante en sql classique :

    Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    select h.*, count(c.id)
    from hotel h
    join chambre c
    on c.hotel_id=h.id
    where h.ville_id=ville_id
    group by h.id

    ça marche niquel !

    Mais sous symfony, pour faire la même chose avec le querybuilder, c'est un vrai calvaire :

    Dans le repository Hotel, je mets le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    $qb= $this->createQueryBuilder('h');
     
    		        $qb->addSelect('COUNT(c.id)')
                            ->join('h.chambres','c')
                            ->where('h.ville = :ville')
                            ->groupBy('c.hotel')
    			->setParameter('ville',$ville)
                            ->getQuery()->getResult();
    Et lorsque dans mes vues j'essaie d'afficher les informations de chaque hotel :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    {{hotel.nom }}, {{hotel.classment }}
    je reçois l'exception suivante :

    Key "nom" for array with keys "0, 1" does not exist in EuropeVoyageBundle::listeHotels.html.twig at line 9
    J'aurais grand besoin d'aide pour identifier et régler mon problème

  2. #2
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Juin 2011
    Messages
    725
    Détails du profil
    Informations personnelles :
    Sexe : Homme

    Informations forums :
    Inscription : Juin 2011
    Messages : 725
    Points : 1 050
    Points
    1 050
    Par défaut
    Bonjour,

    Lorsque les résultats d'une requete DQL comportent à la fois un objet et d'autres valeurs , chaque ligne est un tableau avec à l'index 0 l'objet.

    http://docs.doctrine-project.org/en/...-mixed-results

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    {%for result in results%}
         {%set hotel = result[0]%}
         {%set countChambres = result[1]%}
        {{hotel.nom }}, {{hotel.classment }}, {{countChambres}}
    {%endfor%}

  3. #3
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    merci,

    Effectivement, j'avais remarqué en faisant un var_dump($results) que j'avais un tableau avec 0->mes entités, 1->mes count, mais je ne savais pas comment me sortir de ça

    Cela fonctionne bien lorsque je les réaffiche avec twig {% set h= result[0] %}

    Mais est-ce quand-même propre comme méthode ?

  4. #4
    Membre expérimenté Avatar de Nico_F
    Homme Profil pro
    Développeur Web
    Inscrit en
    Avril 2011
    Messages
    728
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Avril 2011
    Messages : 728
    Points : 1 310
    Points
    1 310
    Par défaut
    Hello,

    Ce n'est pas "propre" dans la mesure ou ça peut être évité, mais ce n'est pas non plus un crime de lèse-majesté.

    Dans ton cas tu pourrais faire un addSelect('c') pour remplacer le count. Tu charges un peu plus ton graphe d'objet, mais tu vas bien récupérer des objets et plus des tableaux.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    $qb= $this->createQueryBuilder('h');
    $qb->addSelect('c')
        ->join('h.chambres','c')
        ->where('h.ville = :ville')
        ->groupBy('c.hotel')
        ->setParameter('ville',$ville)
        ->getQuery()->getResult();
    Ca te permettrait d'avoir dans ton twig quelque chose d'un peu plus élégant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    {%for hotel in results%}
        {{hotel.nom }}, {{hotel.classment }}, {{ hotel.chambres|length }}
    {%endfor%}
    Et puis ça évite de faire appel au lazy loading si jamais tu utilises des propriétés de chambres.

    Si vraiment la jointure est trop énorme et renvoie inutilement une quantité astronomique d'objets dans la collection, alors personnellement je préfère faire une autre requête pour le count, dans une variable à part, ou je retourne les différents counts, indexés par l'id de l'entité principale. C'est pas forcément plus élégant, mais ma collection reste une collection d'objets propre.

  5. #5
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Merci pour vos indications

  6. #6
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Petit problème cela dit :

    Lorsque je fais :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    $qb= $this->createQueryBuilder('h');
    $qb->addSelect('c')
        ->join('h.chambres','c')
        ->where('h.ville = :ville')
        ->groupBy('c.hotel')
        ->setParameter('ville',$ville)
        ->getQuery()->getResult();
    Lorsque dans ma vue j'essaie d'afficher :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    {{ h.chambres.count }} chambres libres
    ou meme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    {{ h.chambres | length }} chambres libres
    Cela m'affiche toujours "1 chambres libres" et non pas le nombre de chambres souhaitées, c'est à dire que le count ne s'effectue pas.

    Comment pourrais-je régler cela ?

  7. #7
    Membre expérimenté Avatar de Nico_F
    Homme Profil pro
    Développeur Web
    Inscrit en
    Avril 2011
    Messages
    728
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Avril 2011
    Messages : 728
    Points : 1 310
    Points
    1 310
    Par défaut
    ... pour commencer histoire de voir quelle tête a ta collection.

  8. #8
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Alors,

    Lorsque je {{ dump(h.chambres) }}

    Je m'aperçois que j'ai un objet "Persistent Collection", qui est un tableau de taille 1, voilà pourquoi le "h.chambres.count" me renvoie 1 je pense.

    Je ne sais pas vraiment comment me sortir de ça

  9. #9
    Membre expérimenté Avatar de Nico_F
    Homme Profil pro
    Développeur Web
    Inscrit en
    Avril 2011
    Messages
    728
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Avril 2011
    Messages : 728
    Points : 1 310
    Points
    1 310
    Par défaut
    Il te renvoie une collection avec un élément dedans ok, est-ce que cet élément est bien un objet Chambre ?

    Si c'est le cas pourquoi il n'en renvoie pas plus : est-ce que ta requête est erronée ? Ou est-ce que c'est normal parce que dans ton jeu de données cet hotel ne comporte qu'une chambre ?

  10. #10
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Juin 2011
    Messages
    725
    Détails du profil
    Informations personnelles :
    Sexe : Homme

    Informations forums :
    Inscription : Juin 2011
    Messages : 725
    Points : 1 050
    Points
    1 050
    Par défaut
    Sans le groupBy ça devrait fonctionner
    $qb= $this->createQueryBuilder('h');
    $qb->addSelect('c')
    ->join('h.chambres','c')
    ->where('h.ville = :ville')
    ->groupBy('c.hotel')
    ->setParameter('ville',$ville)
    ->getQuery()->getResult();

    Ton questionnement initial était de savoir si la première solution était "propre", Disons qu'elle était au moins optimisé puisque tu ne récupérait pas l'ensemble des entités chambres (ce qui peut prendre beaucoup de mémoire)


    Sur des problématiques comme celle-ci je crée parfois un attribut non persistant dans l'entité
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    MyEntity{
       protected $vars=array();
       public setVar($name,$value){
         $this->vars=is_array($this->vars)?$this->vars:array();
         $this->vars[$name]=$value;
      }
      public function getVar($name){
          $this->vars=is_array($this->vars)?$this->vars:array();
         return isset($this->vars[$name])?$this->vars[$name]:null;
      }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    /**
    * @return tableau d'entité Hotel
    * (mais pourrait renvoyer toute variable sur laquelle on peut faire un foreach)
    **/
    MyEntityRepository{
        public function findWithCountChambres(){
                 $qb= $this->createQueryBuilder('h'); 
                 $result=$qb->addSelect('COUNT(c.id)')
                            ->join('h.chambres','c')
                            ->where('h.ville = :ville')
                            ->groupBy('c.hotel')
    			->setParameter('ville',$ville)
                            ->getQuery()->getResult();
                 $entities=array();
                  foreach($result as $a){
                      $hotel=$a[0];
                      $count=$a[1];
                      $hotel->setVar('countChambres',$count);
                      $entities[]=$hotel;
                  }
                return $entities;
       }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    {%for h in hotels%}
       {{h.getVar('countChambres')}}
    {%endfor%}
    L'idée c'est de pouvoir stocker des informations supplémentaires dans l'entité.
    Je met un attribut de type array parce que aujourd'hui tu veux juste le nombre de chambres par hotel, mais la demande peut évoluer: afficher le nbre de chambres + nbre de chambres dispo + nbre de chambres par nbre de lit et à ce moment là il faudra bien faire agir au niveau de ta requete pour récupérer ces informations

    ---

    Au passage, il existe une annotation EXTRA_LAZY pour le chargement des relations (on a toujours le même nbre de requetes mais celle-ci ne récupère que l'information utile)
    http://docs.doctrine-project.org/en/...ociations.html
    avec lazy loading simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    {{h.chambres | length}}//effectue un SELECT * FROM chambre WHERE hotel_id=id
    avec extra lazy
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    {{h.chambres | length}}//effectue un SELECT COUNT(*) FROM chambre WHERE hotel_id=id

  11. #11
    Membre expérimenté Avatar de Nico_F
    Homme Profil pro
    Développeur Web
    Inscrit en
    Avril 2011
    Messages
    728
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Avril 2011
    Messages : 728
    Points : 1 310
    Points
    1 310
    Par défaut
    +1 pour le groupBy (j'avais pas lu la requête)

    D'accord pour dire que la jointure avec chambre est une solution à n'utiliser que si la quantité d'objets le permet car ça augmente la quantité de mémoire utilisée.

    Ajouter un attribut non mappé, pourquoi pas, disons que ce n'est pas scandaleux dans certains cas, mais pour un count je trouve que c'est un peu goret.

    Par contre la boucle en dessous de la requête DQL je suis pas fan du tout. Autant en mémoire qu'en temps d'exécution (surtout s'il y a beaucoup de résultats).
    À ce compte je préfère faire une deuxième requête juste pour le count qui serait moins couteuse sur les deux plans.

    J'aime beaucoup la solution du extra_lazy : je ne connaissais pas, mais ça semble bien plus propre.
    Si tu n'as pas du tout besoin d'avoir la collection c'est cette solution que je suggèrerais.

    ++

  12. #12
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Juin 2011
    Messages
    725
    Détails du profil
    Informations personnelles :
    Sexe : Homme

    Informations forums :
    Inscription : Juin 2011
    Messages : 725
    Points : 1 050
    Points
    1 050
    Par défaut
    Ajouter un attribut non mappé, pourquoi pas, disons que ce n'est pas scandaleux dans certains cas, mais pour un count je trouve que c'est un peu goret.
    en fait quand j'ai commencé à avoir besoin de ce genre de donnée je faisais ça
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    protected $countChambres;
    public getCountChambres(){
      if(null===$this->countChambres){
        $this->countChambres=count($this->chambres);
      } 
      return $this->countChambres;
    }
    public function setCountChambres($count){
       $this->countChambres=$count;
    }
    }
    l'avantage étant que l'on peut laisser faire le (extra) lazy loading si on travaille sur une seule entité ou bien le remplir dans le repository avec une seule requete pour +sieurs entités
    Je suis passé sur un attribut de type tableau, car c'est quand même un des interet du SGBDR de récupérer des valeurs aggrégé (count, sum, etc...), que celles-ci sont forcément à géométrie variable et que l'on risque de se retrouver avec une tripotée d'attributs rarement utilisés

    Par contre la boucle en dessous de la requête DQL je suis pas fan du tout.
    En fait j'utilise une classe dans ce gout là pour éviter cette itération supplémentaire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
     
    class CallbackIterator implements Iterator
    {
        /**
         *
         * @var Iterator
         */
        private $iterator;
     
        /**
         *
         * @var callable
         */
        private $callback;
     
        function __construct($iterator, $callback)
        {
            if (is_array($iterator)) {
                $this->iterator = new \ArrayIterator($iterator);
            } elseif ($iterator instanceof IteratorAggregate) {
                $this->iterator = $iterator->getIterator();
            } elseif ($iterator instanceof \Iterator) {
                $this->iterator = $iterator;
            } else {
                throw new \Exception('an iterator is expected');
            }
            if (!is_callable($callback)) {
                throw new \Exception('a callable is expected');
            }
            $this->callback = $callback;
        }
        /**
        * C'est ici que ça se passe!
        **/
        public function current()
        {
            $row = $this->iterator->current();
            return call_user_func($this->callBack, $row);
        }
     
        public function key()
        {
            return $this->iterator->key();
        }
     
        public function next()
        {
            return $this->iterator->next();
        }
     
        public function rewind()
        {
            return $this->iterator->rewind();
        }
     
        public function valid()
        {
            return $this->iterator->valid();
        }
     
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    /**
    * @return n'importe quoi du moment que l'on peut faire un foreach dessus 
    **/
    public function getHotelWithCountChambres(){
    $qb= $this->createQueryBuilder('h'); 
                 $result=$qb->addSelect('COUNT(c.id)')
                            ->join('h.chambres','c')
                            ->where('h.ville = :ville')
                            ->groupBy('c.hotel')
    			->setParameter('ville',$ville)
                            ->getQuery()->getResult();
              return new CallbackIterator($result,function($row){
                  $hotel=$row[0];
                  $hotel->setVar('countChambres',$row[1];
                  return $hotel;
             }
    }

  13. #13
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Je me rends compte en fait qu'il y a un problème avec le addSelect, qui ne me retourne toujours qu'une seule ligne

    En fait mon concept est de récupérer le nombre de chambres libres, mais à une date donnée par l'utilisateur (pour voir si la réservation est libre)

    Je vais donc donner le code complet de ma requête :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    $qb=$this->createQueryBuilder('h');
     
    		$list=$qb
                            ->addselect('c')
                            ->where('h.ville = :ville')
                            ->join('h.chambres','c')
                            ->leftJoin('c.usersChambres','r','WITH',$qb->expr()->orX(
                                $qb->expr()->between(':datedep','r.dateDebut','r.dateFin'),
                                $qb->expr()->between(':dateret','r.dateDebut','r.dateFin'),
                                $qb->expr()->between('r.dateDebut',':datedep',':dateret'),
                                $qb->expr()->between('r.dateFin',':datedep',':dateret')
                                ))
                            ->andWhere('r.user IS NULL')
                            ->groupBy('c.hotel')
                            ->having('count(c.id) >= :nbch')
    			->setParameters(array('ville'=>$ville,'nbch'=>$nbch, 'datedep'=>$datedep, 'dateret'=>$dateret))
                            ->getQuery()->getResult();
    Donc ensuite quand je fais un var_dump sur mes hotels récupérés, je vois qu'il ne récupère q'une seule chambre par hotel et ceci lorsque j'ajoute addSelect('c')

    Quoi qu'il arrive même lorsque je fais plusieurs tests le addSelect ne me récupère toujours qu'une seule ligne, c'est donc à ce niveau que ça pêche, il doit certainement être mal utilisé

  14. #14
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Juin 2011
    Messages
    725
    Détails du profil
    Informations personnelles :
    Sexe : Homme

    Informations forums :
    Inscription : Juin 2011
    Messages : 725
    Points : 1 050
    Points
    1 050
    Par défaut
    Comme dit plus haut avec le group by tu ne recuperes qu'une seule ligne par hotel et donc une seule chambre.
    A priori tu en a forcément besoin, puisque tu utilise une clause having.

    Bref il me semble que ton code initiale était le plus à même de faire ce que tu veux

  15. #15
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Mai 2013
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2013
    Messages : 33
    Points : 19
    Points
    19
    Par défaut
    Ok, ça marche en faisant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
                {%set h=hotel[0] %}
                {%set nbc=hotel[1] %}
    Donc je vais m'en tenir à ça

    Je vous remercie c'était sympa de vous être penché sur mon problème

Discussions similaires

  1. Réponses: 10
    Dernier message: 14/04/2011, 13h47
  2. Réponses: 4
    Dernier message: 06/11/2007, 10h58
  3. [Interbase] select ne fonctionne pas
    Par xclam dans le forum PHP & Base de données
    Réponses: 1
    Dernier message: 30/04/2007, 19h01
  4. Réponses: 7
    Dernier message: 10/01/2006, 11h27
  5. mon select count(*) marche pas
    Par zorba49 dans le forum Langage SQL
    Réponses: 14
    Dernier message: 05/08/2005, 09h28

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo