Bonne pratique (requete COUNT - afficher nombres d'articles par catégories)

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour, je viens demander un renseignement Svp.

Dans un CMS fait maison, j'ai une page qui affiche la liste des articles, et dans cette page il y a un <select> pour pouvoir filtrer par catégorie. Et dans ce <select>, j'affiche entre des parenthèses le nombre d'articles que cette catégories contient.

Mon code marche, mais je ne sais pas si j'utilise la meilleur manière. Voici mon Code: HTML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<form id="form-categories" action="#" method="get">
    <select onchange="document.getElementById('form-categories').submit();" name="categorie">
        <?php
        // ** req SQL SELECT pour récup liste des catégories **
        $data2 = $categorieArticle->selectListeCategories();

        foreach($data2 as $data_cat) {

            // ** req SQL COUNT pour afficher nombre d'articles par catégorie (entre les parenthèse, dans <select>) **
            $data_count = $article->nbArticlesParCategorie($data_cat->id);

            echo '<option value="'.$data_cat->id.'" '.$selected.'>'.$data_cat->nom.' ('.$data_count.')</option>';

        }
        ?>
    </select>
</form>

Et PHP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
/**
* Requete SQL SELECT COUNT pour affiher le nombre d'articles par catégorie (entre les parenthèse, dans <select>)
*/
public function nbArticlesParCategorie($id_cat)
{                       
    $sql = "SELECT  COUNT(*) as nb
            FROM ".$this->_tableArticles."
            INNER JOIN ".$this->_tablePivotCategoriesJointesArticles."
                ON ".$this->_tableArticles.".id = ".$this->_tablePivotCategoriesJointesArticles.".id_article";
            "WHERE ".$this->_tablePivotCategoriesJointesArticles.".id_categorie = :id_cat";
    $requete = self::getDb()->prepare($sql);

    $requete->bindValue(':id_cat', $id_cat);
    $requete->execute();

    $result = $requete->fetch(PDO::FETCH_OBJ);
    $requete->closeCursor();
    return (int) $result->nb;
}

Avec ce code, si par exemple il y a 50 catégories, ça me fait faire 50 requêtes SQL COUNT… ça fait pas un peu beaucoup ? Y a t-il une autre solution? (une autre requete SQL qui consomme moins de ressources ?).

Merci beaucoup.

Édité par stephweb

+0 -0
Staff

ça fait pas un peu beaucoup ? Y a t-il une autre solution?

oui.

Tu peux utiliser la clause "GROUP BY" qui te permettra de sélectionner le nombre d'article par catégorie en me^me temps que tu vas chercher les articles, ce qui ferait quelque chose du genre :

1
2
3
4
SELECT c.id, c.nom, c.description, COUNT(*) as nb_article 
FROM categories c
INNER JOIN articles a ON a.category_id = c.id
GROUP BY c.id

et ça te donnerait un tableau comme celui-ci

c.id c.nom c.description nb_article
1 Première catégorie Ceci ou celà 5
2 Autre catégorie Une description 10

Édité par artragis

+2 -0

Ajouter et maintenir un compteur à ta table de catégories ? (counter_cache) Comme ça, pas besoin de plus qu'un SELECT * FROM categories.

Par trigger ou code, à l'ajout d'un article : incrémentation du compteur ; à la suppression : décrémentation.

Édité par vibrice

+1 -0

Ajouter et maintenir un compteur à ta table de catégories ? (counter_cache) Comme ça, pas besoin de plus qu'un SELECT * FROM categories.

Par trigger ou code, à l'ajout d'un article : incrémentation du compteur ; à la suppression : décrémentation.

vibrice

Si tu veux soulager le serveur, c'est effectivement une très bonne méthode, et pas mal de CMS utilisent ce principe (entre autres, puisque tout ou presque est mis en cache).

Auteur du sujet

Ok merci à tous pour vos réponses. En fait j'ai ceci: http://www.creerwebsite.com/medias/upload/11-10-2015-22H-32m-50s_testp.png Dans cet exemple, ce <select> me fait d'abord faire une requete SQL SELECT pour récupérer la listre des catégories, et me fait faire aussi 4 requetes SQL count…

C'est pour ça que je suis venu me renseigner sur ce forum. counter_cache ou GROUP BY, je ne sais pas quoi faire. Merci

Édité par stephweb

+0 -0
Auteur du sujet

Du coup avec un GROUP BY ça me donne ceci:

 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
<?php
/*
* Requete SQL SELECT pour récupérer la liste des catégories,
* et aussi récupérer le nombre d'articles par catégorie
* @param statut - 1: Articles publiés, 2 : articles en brouillons, 3: articles dans la corbeille
*/
public function selectListeCategories($statut)
{
   $sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
         , COUNT(*) as nb_article
   FROM ".$this->_tableCategoriesArticles."
   INNER JOIN ".$this->_tableCategoriesJointesArticles."
      ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie
   GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom";

   $requete = self::getDb()->prepare($sql);
   $requete->execute();
   $requete->setFetchMode(PDO::FETCH_OBJ);
   return $requete;
}
/*
$this->_tableCategoriesArticles : Est ma table où il y a mes catégories (que je peut relier à mes articles)
$this->_tableArticles : Est ma table où il y a mes articles
$this->_tableCategoriesJointesArticles : Est ma table intermédiaire, qui relie ma table articles à ma table categories
*/

Par contre, le fait d'utiliser un GROUP BY, ça me pose un autre problème. Car ça m'affiche le nombre d'articles par catégorie, OK. Mais le problème c'est que j'ai des articles avec le statut publié, d'autre en brouillons, d'autre dans la corbeille… Svp, Comment puis-je faire pour que je suis ajouter un WHERE statut à ce code ?

Il me faudrai un truc comme ceci:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
public function selectListeCategories($statut)
{
   $sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
         , COUNT(*) as nb_article
   FROM ".$this->_tableCategoriesArticles."
   INNER JOIN ".$this->_tableCategoriesJointesArticles."
      ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie
      WHERE dans la table articles statut = :statut
   GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom";

   $requete = self::getDb()->prepare($sql);
   $requete->bindValue(':statut', $statut, PDO::PARAM_INT);
   $requete->execute();
   $requete->setFetchMode(PDO::FETCH_OBJ);
   return $requete;
}

Mais je n'arrive pas comment le finaliser. Merci beaucoup.

Édité par stephweb

+0 -0
Auteur du sujet

ok merci, mais "$this->_tableCategoriesJointesArticles" c'est ma table intermédiaire, qui a comme champs: id_article et id_categorie. Elle a donc pas de champs statut, et PDO me renvoi donc cette erreur: Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'categories_jointes_articles.statut' in 'where clause''…

Le champs statut est présent dans ma table "$this->_tableArticles", j'ai donc essayé ce code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
$sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
        , COUNT(*) as nb_article 
FROM ".$this->_tableCategoriesArticles."
INNER JOIN ".$this->_tableCategoriesJointesArticles."
    ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie
    WHERE ".$this->_tableArticles .".statut = :statut
GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom";

$requete = self::getDb()->prepare($sql);
$requete->bindValue(':statut', $statut, PDO::PARAM_INT);
$requete->execute();
$requete->setFetchMode(PDO::FETCH_OBJ);
return $requete;

Mais PDO me renvoi ceci: Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'articles.statut' in 'where clause'' in… Je ne voit vraiment pas comment faut faire. Merci d'avance

Édité par stephweb

+0 -0
Auteur du sujet

Du coup, fallais rajouter une jointure, ce qui fait:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
$sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
        , COUNT(*) as nb_articles
FROM ".$this->_tableCategoriesArticles."
INNER JOIN ".$this->_tableCategoriesJointesArticles."
    ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie

INNER JOIN ".$this->_tableArticles."
    ON ".$this->_tableArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_article
    WHERE ".$this->_tableArticles.".statut = :statut

GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
ORDER BY id DESC";

$requete = self::getDb()->prepare($sql);
$requete->bindValue(':statut', $statut, PDO::PARAM_INT);
$requete->execute();
$requete->setFetchMode(PDO::FETCH_OBJ);
return $requete;

Il reste un dernier problème, ça ne m'affiche pas toutes les catégories. ça m'affiche uniquement les catégories qui sont reliées à des articles. C'est normal ? Y'a t-il un moyen, dans la liste des catégories, d'afficher toutes les catégories y compris celles qui ne sont pas reliées à des articles ? Merci

Édité par stephweb

+0 -0
Auteur du sujet

Ok merci. Si je remplace INNER JOIN par LEFT OUTER JOIN, ça m'affiche toute mes catégories. Mais pour mes catégories qui n'ont aucun article, ça m'affiche 1 ou lieu de 0 pour le count (comme quoi mes catégories qui sont sans article ont 1 article, ce qui est évidement pas le cas…). Si je remplace LEFT OUTER JOIN par RIGHT OUTER JOIN, ça m'affiche seulement mes catégories ayant des articles (donc ça ne change rien par rappoet au INNER JOIN). J'ai fait une erreur? Merci

Édité par stephweb

+0 -0
1
2
3
4
5
<form id="form-categories" action="#" method="get">
  <select onchange="document.getElementById('form-categories').submit();" name="categorie">
      <?php
      // ** req SQL SELECT pour récup liste des catégories **
      $data2 = $categorieArticle->selectListeCategories();

C'est pas le sujet mais tu mélange html, js et php en 5 lignes de code. Tu devrais séparer tout ça.

Édité par Umbra

+0 -0

Salut !

Il reste un dernier problème, ça ne m'affiche pas toutes les catégories. ça m'affiche uniquement les catégories qui sont reliées à des articles. C'est normal ? Y'a t-il un moyen, dans la liste des catégories, d'afficher toutes les catégories y compris celles qui ne sont pas reliées à des articles ? Merci

stephweb

Il ne faut pas faire count(*), à mon avis, mais plutôt count(articles.id), avec le LEFT OUTER JOIN

Evitez qu'on vous dise de les lire : FAQ PHP et Symfony 2Tutoriel WAMP • Cliquez 👍 pour dire merci • Marquez vos sujets résolus

+1 -0

Je te conseilles de bosser en SQL avant de transposer en PHP.

Perso j'aurais corrigé ton SQL comme ceci.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
SELECT
    tableCategoriesArticles.id, 
    tableCategoriesArticles.nom, 
    COUNT(tableArticles.id) as nb_articles
FROM tableCategoriesArticles
LEFT OUTER JOIN tableCategoriesJointesArticles
    ON tableCategoriesArticles.id = tableCategoriesJointesArticles.id_categorie
LEFT OUTER JOIN tableArticles
    ON tableArticles.id = tableCategoriesJointesArticles.id_article
WHERE tableArticles.statut = :statut
GROUP BY tableCategoriesArticles.id -- , tableCategoriesArticles.nom
ORDER BY id DESC;

EDIT: bah grilled…

Édité par yoch

+1 -0
Auteur du sujet

Ok merci. @yoch ça fonctionne uniquement si j'enlève la 2ème jointure. donc ce n'est pas bon. Car avec ton code, ça m'affiche uniquement les catégories qui sont reliés à des articles. J'ai essayé de remplacer les LEFT par des RIGHT, mais ça ne change rien…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
$sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
        , COUNT(id_article) as nb_articles
FROM ".$this->_tableCategoriesArticles."
LEFT OUTER JOIN ".$this->_tableCategoriesJointesArticles."
    ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie

LEFT OUTER JOIN ".$this->_tableArticles."
    ON ".$this->_tableArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_article
    WHERE ".$this->_tableArticles.".statut = :statut

GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom";

$requete = self::getDb()->prepare($sql);
$requete->bindValue(':statut', 1, PDO::PARAM_INT);
$requete->execute();
$requete->setFetchMode(PDO::FETCH_OBJ);
return $requete;

Pourtant mon code à l'air propre. mais je n'arrive vraiment pas à le faire fonctionner…

+0 -0

Ça doit simplement être à cause du WHERE (qui est appliqué au niveau global contrairement à ce que ton indentation laisse supposer), essaie de le remplacer par un AND (qui sera appliqué dans le JOIN).

Édité par yoch

+0 -0
Auteur du sujet

Ok merci. effectivement, avec ce code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
$sql = "SELECT ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom
        , COUNT(id_article) as nb_articles
        FROM ".$this->_tableCategoriesArticles."
        LEFT OUTER JOIN ".$this->_tableCategoriesJointesArticles."
            ON ".$this->_tableCategoriesArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_categorie

        LEFT OUTER JOIN ".$this->_tableArticles."
            ON ".$this->_tableArticles.".id = ".$this->_tableCategoriesJointesArticles.".id_article
            AND ".$this->_tableArticles.".statut = :statut

        GROUP BY ".$this->_tableCategoriesArticles.".id, ".$this->_tableCategoriesArticles.".nom";

$requete = self::getDb()->prepare($sql);
$requete->bindValue(':statut', 1, PDO::PARAM_INT);
$requete->execute();
$requete->setFetchMode(PDO::FETCH_OBJ);
return $requete;

ça m'affiche toute les catégories, Mais le count ne fonctionne plu correctement. Mont but: est d'afficher toute mes catégories (y compris celles qui ne sont pas reliées à des articles), et aussi de conter le nombre d'article par catégorie ayant le statut 1. C'est possible de faire tout celà en une requête ? Merci beaucoup

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte