2 requêtes et boucles WHILE

Eléphanteau du PHP | 47 Messages

25 juil. 2010, 17:03

Bonjour,

J'ai une base de données MYSQL qui répertorie mes livres et qui comporte plusieurs tables dont les trois suivantes :
- 1 table LIVRE comportant notamment comme champs : ID_LIVRE et TITRE
- 1 table CATEGORIE comportant comme champs : ID_CATEGORIE et NOM_CATEGORIE (c-a-d roman, document, essais etc)
- 1 table CLASSIFIE3 comportant comme champs : ID_LIVRE et ID_CATEGORIE

En utilisant la requête suivante :
SELECT L.TITRE, C.NOM_CATEGORIE
FROM LIVRE L
INNER JOIN CLASSIFIE3 A
    ON A.ID_LIVRE = L.ID_LIVRE
INNER JOIN CATEGORIE C
    ON A.ID_CATEGORIE = C.ID_CATEGORIE
1000 résultats (livres) me sont retournés alors que ma bibliothèque ne contient que 800 livres. Mais c'est normal car un livre peut appartenir à plusieurs catégories. Dans ma page PHP, il y aura donc autant d'occurrences du même livre que de catégories auquel appartient ce livre :
ex : 1- "LE FLEAU"
categorie : A
2- "LE FLEAU"
categorie : B

Ce n'est pas ce que je veux obtenir. Si j'affiche la liste de mes livres, je souhaite qu'il ne s'en affiche que 800 (nombre réel). Pour reprendre l'exemple précédent, je souhaite que le titre "LE FLEAU" n'apparaisse qu'une seule fois mais avec les 2 catégories auquel il appartient :
soit :1- "LE FLEAU"
categorie : A;B

J'ai essayé le code suivant avec 2 requêtes mais cela ne fonctionne pas :
<?php
try
{
	
	$bdd = new PDO('mysql:host=localhost;dbname=bibliosql', 'root', '');
}
catch(Exception $e)
{
	
        die('Erreur : '.$e->getMessage());
}




$reponse = $bdd->query('SELECT livre.titre

FROM LIVRE

LIMIT 0,50');

$reponse2 = $bdd->query('SELECT L.TITRE, C.NOM_CATEGORIE
FROM LIVRE L
INNER JOIN CLASSIFIE3 A
    ON A.ID_LIVRE = L.ID_LIVRE
INNER JOIN CATEGORIE C
    ON A.ID_CATEGORIE = C.ID_CATEGORIE

LIMIT 0,50');



while ($donnees = $reponse->fetch())

{
?>
    <p>
    <strong>TITRE</strong> : <?php echo $donnees['titre']; ?><br />
    
   </p>
   
   
<?php
while ($donnees2 = $reponse2->fetch())

{

?>
   <p> <strong>CATEGORIE</strong> : <?php echo $donnees2['NOM_CATEGORIE']; ?><br />
    
   </p>

<?php
}
}
$reponse->closeCursor(); 
$reponse2->closeCursor(); 

?>
Cela m'affiche le titre de mon 1er livre, puis toutes les catégories attachées à mes livres (1000), puis cela reprend la liste de mes livres à partir du 2eme et suivants.

Pouvez-m'aider à obtenir ce que souhaite ? Merci d'avance et pardon d'avoir été aussi long.

Almoha

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

25 juil. 2010, 17:18

Tu peux inclure une seconde requête et pour chaque livre aller chercher la ou les catégories auxquelles il est lié, mais cela risque de faire un nombre important de requête. A ta place j'éviterais donc la seconde requête et resterais sur ton premier code.
Pour éviter que le livre ne se répète, il te suffit d'utiliser une variable temporaire en php qui contiendra le nom du précédent livre affiché. Si celui-ci est différent du livre en cours, tu affiches le nom du nouveau livre. S'ils sont identique, tu affiches uniquement la catégorie :)

Grosso modo, tu aurais quelque chose de ce genre :
$oldBook = '';
while (...) {
  if ($book != $oldBook) { // s'il s'agit d'un nouveau titre
     echo $book; // affiche le titre du livre
     $oldBook = $book; // stock le nom du livre courrant
  }

  echo $catégorie; // affiche la catégorie courrante
}
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphanteau du PHP | 47 Messages

25 juil. 2010, 21:50

Bonsoir,

Merci pour la réponse. J'ai voulu tester la solution sur 2 autres de mes tables :
- 1 table genre comportant comme champs : id_genre et nom_genre (c-a-d thriller, SF, fantasy etc)
- 1 table thematise comportant comme champs : id_genre et id_livre
(et toujours 1 table livre comportant notamment comme champs : id_livre et titre)

Voici mon code :
<?php

try
{
	
	$bdd = new PDO('mysql:host=localhost;dbname=bibliosql', 'root', '');
	
}
catch(Exception $e)
{
	
        die('Erreur : '.$e->getMessage());
}


$reponse = $bdd->query('SELECT L.titre, G.nom_genre
FROM livre L
INNER JOIN thematise T
    ON T.id_livre = L.id_livre
INNER JOIN genre G
    ON T.id_genre = G.id_genre

LIMIT 0,500');

$oldBook = '';
while ($donnees = $reponse->fetch())

{
if ($donnees['titre']  != $oldBook)  { // s'il s'agit d'un nouveau titre
     echo $donnees['titre'].'<br/>';// affiche le titre du livre
     $oldBook = $donnees['titre']; // stock le nom du livre courrant
	 }
	 echo $donnees['nom_genre'].'<br/>';  // affiche la catégorie courrante
	 


}

$reponse->closeCursor(); 

?>
    
Mais à l'affichage, je n'arrive pas au résultat escompté, à savoir par exemple :

"LE FLEAU"
Fantastique
"LE FLEAU"
Horreur

au lieu de

"LE FLEAU"
Fantastique
Horreur

Je ne vois pas le problème dans le code. Pouvez-vous m'aider ? Merci

Almoha

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

25 juil. 2010, 23:04

Ajoute un "order by titre" dans ta requete avant le limit. Pour que cela fonctionne il faut que les infos d'un même livre se suivent... :)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphanteau du PHP | 47 Messages

26 juil. 2010, 10:31

Bonjour,

Effectivement avec le order by titre, cela fonctionne. Mais mon site prévoira divers tris (par date d'acquisition, par auteur etc) et du coup l'astuce ne marchera plus. Dans ta première réponse, tu parlais d'inclure une 2nd requête...Je suis débutant et je ne vois pas comment arriver à l'affichage souhaité compte tenu des divers tris prévus. Merci de ton aide complémentaire.

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

26 juil. 2010, 23:30

Dans ce cas, tu peux effectivement faire une première requête qui va te retourner tous les titres des livres, puis une boucle pour parcourir les résultats. A partir de là, dans ta boucle, pour chacun des résultats, tu peux faire une requête pour aller chercher les catégories du livre concerné.

L'inconvénient de cette méthode, c'est que 800 livres, ça fait 800 requêtes et c'est pas top d'un point de vue performance.

A la limite, tu peux garder le principe sauf qu'au lieu de faire une requête pour chaque livre, tu fais au préalable une requête qui ramène toutes les catégories et les liens entre les livres et les catégories. Tu stockes le tout dans un tableau associatif de façon à pouvoir l'interroger depuis ton while :
ex : array ( idLivre1 => (cat1, cat3), idLivre2 => (cat1), ...)

pour chacun de tes livres, tu pourrais alors récupérer le tableau de catégories associé à son id. Ca reste le même principe, mais cela te permet de limiter à 2 ou 3 requêtes et php fera le reste ce qui sera beaucoup plus léger sur un grand volume de données :)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphanteau du PHP | 47 Messages

26 juil. 2010, 23:55

Merci pour cette réponse très claire. Mais si je comprend la théorie, j'avoue éprouver de grandes difficultés pour mettre en pratique dans mon code ce que tu exposes. Si j'ai bien compris ma 1ere requête doit se limiter à cela :
SELECT L.titre 
FROM livre L
LIMIT 0,50');
Dois-je ensuite faire la 2nde requête suivante :
SELECT L.TITRE, C.NOM_CATEGORIE
FROM LIVRE L
INNER JOIN CLASSIFIE3 A
    ON A.ID_LIVRE = L.ID_LIVRE
INNER JOIN CATEGORIE C
    ON A.ID_CATEGORIE = C.ID_CATEGORIE
LIMIT 0,50');
Ensuite vient le stockage du tableau associatif et l'interrogation depuis le while ? Mais là j'avoue ne pas savoir faire concrètement dans mon code.
J'espère ne pas abuser en te demandant de m'aider à écrire le bout de code qui correspond à ta réponse. D'avance merci.