Classer les résultats d'un count (2 tables)

Liza
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 04:19

Bonjour chère communauté,

Après 3 jours de recherche sur tous les forums imaginables, je me résouds à vous soumettre mon problème...

J'ai un système de news qui permet aux membres de voter pour chaque news (vote positif, ou vote négatif), afin d'évaluer chaque news. Je voudrais faire un TOP 10 des news les mieux notées (celles qui ont le plus de votes positifs).

J'ai 2 tables dans ma base de données :
- une table "article" (les news) avec les valeurs suivantes : news_id, news_article
- une table "article_vote" avec les valeurs suivantes : vote_id, vote_idarticle (l'ID de l'article dans la table "article") et vote_result (si vote_result = 1 il s'agit d'un vote positif, si vote_result = 2 il s'agit d'un vote négatif).

Je me retrouve confrontée à un problème imprévu : je n'arrive pas à classer par ordre des news les mieux notées. Au mieux, j'obtiens les valeurs de chaque news, mais dans le désordre (les news bien notées, mal notées, et celles qui n'ont aucune note), le classement se fait par l'ID (news_id). Impossible de faire aparaître en premier les news les mieux notées. Depuis 3 jours, j'ai apris les UNION, les INNER, et autres jointures... Aussi les multi-SELECT, rien n'y fait, je bloque... Je ne vais pas non plus vous parler du GROUP BY que j'ai tenté en vain...

J'ai tenté 2 méthodes.


La méthode sale :
// Je sélectionne toutes mes news
$sql_news="SELECT news_id FROM article";
$query_news=mysql_query($sql_news);

while($data_news=mysql_fetch_assoc($query_news))
{

// Je comptabilise le nombre de votes positifs de chaque news (valeur "1")
$sql_count="SELECT count(vote_result) FROM article_vote WHERE vote_idarticle='".$data_news['news_id']."' AND vote_result='1'";
$result_count=mysql_query($query) or die (mysql_error()); 
$count=mysql_fetch_array($result_count);

echo "Article : ".$data_news['news_article']." / Votes positifs : ".$count[0];
}
A noter que c'est avec cette méthode que je me rapproche de ce que je veux : j'obtiens les résultats positifs de chaque article, mais pas dans l'ordre des mieux notés.


La méthode propre (ce que j'apprends depuis 3 jours) :

J'en ai déduit que pour pouvoir classer mes résultats dans un ordre prédéfini, il fallait combiner les 2 SELECT (la sélection de mes articles (pour récupérer l'ID), et le résultat de chaque article). En quelque sorte comme ma méthode sale de ci-dessus, mais dans une seule commande :
$query = "SELECT boite_id FROM boite UNION SELECT COUNT(boite_op_rep) from boite,boite_op where boite_op.boite_op_ididee=boite.boite_id AND boite_op.boite_op_rep='1'";
Mais ça me donné un résultat incohérent.

Si vous pouviez me donner un petit coup de pouce, en m'orientant plutôt vers la méthode propre (UNION), à moins que vous pensiez que ma méthode sale n'est pas si mal que ça finalement :D (c'est en tout cas avec celle-ci que je me rapproche de ce que je souhaite).


D'avance merci :wink: En espérant ne pas m'être trompée de forum.

PS : bien vu les smileys spécial Noël :D

ViPHP
AB
ViPHP | 5818 Messages

01 déc. 2007, 05:55

Bonsoir,

Bon il est tard et je rentre juste. En essayant de dire pas trop de bêtises, deux remarques :

Ta méthode que tu qualifies de sale, l'est vraiment (litote). Une requête select dans une boucle, c'est surtout ce que l'on apprend à ne pas faire. Combien de requêtes au total vas tu faire pour obtenir ton résultat ? Autant de lignes que tu auras enregistrées dans ta table articles. Si ta table articles contient 1000 articles, 1000 requêtes :x
C'est pas sale, c'est criminel, tu veux tuer ton serveur ? :twisted:

Ma deuxième remarque concerne la méthodologie. Pourquoi faire deux tables ? D'après la structure de ta deuxième table (si tu as indiqué tous les champs) il apparait que celle-ci est inutile et te complique grandement la vie. Dans ta table news tu pourrais ajouter un champs "vote" que tu incrémentes +1 ou -1 suivant la valeur du vote. Et éventuellement un deuxième champ supplémentaire ex:"count_vote" que tu incrémente de +1 pour chaque vote si tu veux pouvoir comptabiliser le nombre de vote.

Il te resterait ensuite à faire une requête de tes news très simple dans le genre
$sql_news="SELECT * FROM article ORDER BY vote DESC"; pour avoir tes news classées suivant le résultat de leur vote.
S'il n'est pas trop tard pour rajouter un ou deux champs dans ta table "article" ce serait beaucoup plus simple et efficace :wink:

Liza
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 07:45

Bonjour AB,

Merci de l'intérêt que vous portez à mon problème. En effet j'ai pensé à ajouter un champs vote dans la table principale, avec une incrémentation +1/-1. Par contre, j'avais oublié de préciser que seuls des membres enregistrés peuvent voter, et voter une seule fois par news. Du coup, une fois qu'ils ont voté, leur ID se retrouve dans la table des votes (ID_vote, ID_news (liée au vote), ID_membre). Ensuite, quand un membre accède à une news, il y a un count() qui vérifie si le membre a déjà voté ou pas à cette news.

Dans ce cas de figure, votre conseil tient toujours (je vais d'ailleurs en profiter de le mettre en application avant qu'il ne soit "trop tard" effectivement :D ), mais pour empêcher un double vote, je dois donc conserver une 2e table, qui contient l'ID de la news, et l'ID du membre qui a posté ? Ou peut-être y a-t-il là aussi plus simple ?
Ta méthode que tu qualifies de sale, l'est vraiment (litote). Une requête select dans une boucle, c'est surtout ce que l'on apprend à ne pas faire.
Je suis sur PHP depuis un an et il est vrai que je doute souvent de la complexité de mon code par rapport au serveur (j'ai un serveur dédié). Mais je touche du bois, mes sites ne m'ont posé aucun problème jusque là, mais il faut dire aussi que le nombre de visiteurs (et de membres) n'est pas énorme. Mais concrêtement, comment faudrait-il optimiser mon "code sale" ci-dessus ? Multi-SELECT ? C'est juste à titre informatif, je ne vous parle plus du problème des votes. Autrement dit comment ne pas interpréter de SELECT dans une boucle ?


Merci de vos précieux conseils :wink:

Liza
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 07:51

Je ne suis pas feinéante je cherche de mon côté :lol:
Ta méthode que tu qualifies de sale, l'est vraiment (litote). Une requête select dans une boucle, c'est surtout ce que l'on apprend à ne pas faire. Combien de requêtes au total vas tu faire pour obtenir ton résultat ? Autant de lignes que tu auras enregistrées dans ta table articles. Si ta table articles contient 1000 articles, 1000 requêtes :x
Admettons, comme ça, ça irait mieux ? :arrow:
$sql_count="SELECT count(vote_result) FROM article,article_vote WHERE article_vote.vote_idarticle=article.news_id AND article_vote.vote_result='1'"; 
(En admettons bien-sûr que le résultat du vote se trouve toujours sur une 2e table. Même si ce n'est pas la meilleure solution et qu'il ne s'agit que d'une ébauche, j'aimerais juste avoir votre avis :wink: )

Invité
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 11:39

Deux réflexions :
1) pourquoi avoir mis 1 pour un vote positif et 2 pour un vote négatif ; un système +1 -1 aurait été préférable, qui te permettrait de faire des SUM

2) Je n'ai pas compris tes requêtes avec des UNION

Code : Tout sélectionner

SELECT boite_id FROM boite UNION SELECT COUNT(boite_op_rep) from boite
Les UNION, INTERSECTION, MINUS servent à créer des requêtes à partir de tables présentant les mêmes éléments et pas des id avec des comptes. Un exemple fictif :

Code : Tout sélectionner

SELECT prix, reference, vitesse from VOITURE UNION SELECT prix, reference, vitesse from AVION

Modérateur PHPfrance
Modérateur PHPfrance | 7636 Messages

01 déc. 2007, 12:08

D'avance merci :wink: En espérant ne pas m'être trompée de forum.
On va dire que si :wink:
Je déplace... poste nous le schéma des tables pour qu'on puisse faire des tests

/!\ Avant de poster se documenter et rechercher.
Qui ne sait pas rendre un service n'a pas le droit d'en demander.
MaBrute

ViPHP
AB
ViPHP | 5818 Messages

01 déc. 2007, 18:37

Admettons, comme ça, ça irait mieux ? :arrow:
$sql_count="SELECT count(vote_result) FROM article,article_vote WHERE article_vote.vote_idarticle=article.news_id AND article_vote.vote_result='1'"; 
(En admettons bien-sûr que le résultat du vote se trouve toujours sur une 2e table. Même si ce n'est pas la meilleure solution et qu'il ne s'agit que d'une ébauche, j'aimerais juste avoir votre avis :wink: )
Effectivement sur le principe c'est beaucoup mieux :)

Mais normalement cette requête ne te renverras pas tes news. Si je ne me trompe, elle devrait te renvoyer le même résultat que
$sql_count="SELECT count(vote_result) FROM article_vote WHERE vote_result='1'"; 
Il faudrait suivre le conseil de Truc et nous poster le schéma de tes tables car sinon on risque de te donner des conseils non adaptés, comme mon précédent message qui te proposait de supprimer la table vote parce que tu n'avais pas indiquer tous les champs :wink:

Invité
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 21:12

Merci à tous de votre intérêt. Ok je vous posterai le shéma des tables ce soir. Celà dit j'ai réglé le problème depuis (en suivant le premier conseil d'AB). J'ai créé un nouveau champ "note" directement dans la table des news. Par contre, j'ai tout de même gardé la 2e table avec l'ID de la news et l'ID et l'ID du membre (pour éviter que le membre vote 2 fois la même news). Et, pardonnez-moi si je me trompe, mais je ne pouvais pas placer cette condition directement dans la table des news.

En apprenant les jointures depuis quelques jours, je me suis focalisée là-dessus en me disant que c'était la meilleure solution. Mais c'est vrai qu'en PHP il ne faut jamais rester buté sur une solution et une seule, alors qu'il y en a toujours une autre pas loin :wink:


Mais normalement cette requête ne te renverras pas tes news. Si je ne me trompe, elle devrait te renvoyer le même résultat que
$sql_count="SELECT count(vote_result) FROM article_vote WHERE vote_result='1'"; 
Effectivement, si j'ai bien compris, du fait qu'il s'agisse d'un COUNT(), je ne pourrais pas récupérer les valeurs dans les tables (exemple un titre, un article), mais juste un résultat. C'est d'ailleurs pour celà que j'étais partie sur l'idée de faire un SELECT et un SELECT COUNT dans la même requête. Par contre, ta requête AB ne comptera que le nombre de votes positifs, toutes les news confondues, alors que je cherchais justement à compter le nombre de votes positifs, mais par news.



Je vous montrerai le shéma ce soir, basé sur le premier conseil d'AB de ce matin.

Merci à tous en tout cas, y'a pas à dire, je bosse sur PHP depuis 1 an et j'ai toujours essayé de trouver les solutions sur les forums, sans poster de nouveau topic. Mais parfois on est obligé :lol: Je ne me suis pas inscrite sur votre forum car j'ai du poster 2 topics en 1 an. Je m'y inscrirai le jour où je serai moi-même en mesure d'aider les autres (autant dire ce n'est pas encore acquis :lol: ).

A tout à l'heure donc :wink:

Liza
Invité n'ayant pas de compte PHPfrance

01 déc. 2007, 21:13

PS : le message ci-dessus est le mien (Liza). Désolé j'ai oublié de renseigner mon pseudo (oui oui, ça ne serait pas arrivé si je m'étais inscrite... :lol: )

ViPHP
AB
ViPHP | 5818 Messages

01 déc. 2007, 21:21

Sous réserve, l'équivalent de ta première méthode "sale" pourrait ressembler à
"
SELECT count(vote_result) as c, news_id, news_article 
FROM articles
LEFT JOIN  article_vote 
ON vote_idarticle = news_id
WHERE vote_result = 1
GROUP BY news_id 
ORDER BY c DESC
LIMIT 10"
- LIMIT 10 uniquement si tu veux ne lister que les 10 premières news ayant le meilleur score
- J'ai mis news_article dans le SELECT pour que tu puisses récupérer la news. Peut-être news_titre pour ne récupérer que le titre pourrait suffire.

EDIT
Du coup tu devrais pouvoir te passer des champs que je t'ai conseillé de rajouter :P quoique je me demande si tu n'a pas intérêt à les garder pour avoir une requête plus rapide que celle-ci :-k

Tu n'avais pas posté dans la bonne rubrique (sql) donc tu n'a pas pu voir le message qui t'indiquait de poster le schéma te tes tables et donc mon conseil n'était pas nécessairement le plus adapté* puisqu'il faut que tu conserve ta deuxième table :wink:
* Valable aussi pour ce message.

Invité
Invité n'ayant pas de compte PHPfrance

02 déc. 2007, 00:15

Sous réserve, l'équivalent de ta première méthode "sale" pourrait ressembler...
AB, c'est exactement ce que je cherchais ! 3 jours pour enfin trouver le "bon SELECT". Après 3 jours de recherche, je suis bien-sûr tombée sur le principe des jointures. Je n'ai jamais essayé votre méthode car j'admets ne pas avoir bien assimilé le principe des "tables de gauche" et "table de droite" (mais je m'y attelle !). En effet celà pourra m'éviter de faire des SELECT dans une boucle... :roll:

Il me reste néanmoins un petit souci : malgré un DESC LIMIT, il ne me sort qu'un seul résultat (le plus fort, celui qui est le mieux noté). Ci-dessous mon code selon ton dernier conseil, et aussi comme promis le shéma de mes tables (j'ai préféré une image, pour plus de clarté, j'espère que ceci n'est pas dérangeant :wink: ) :

Image

Ce qui donne :
$sql="SELECT count(vote_result) as c, boite_id, boite_titre  
FROM boite 
LEFT JOIN  boite_vote 
ON vote_idboite = boite_id 
WHERE vote_result = 1 
GROUP BY boite_id  
ORDER BY c DESC 
LIMIT 5";

$query=mysql_query($sql);
$data = mysql_fetch_array($query);

echo "Idée : ".$data['boite_titre']." / Nombre de votes positifs : ".$data['0']";
Du coup tu devrais pouvoir te passer des champs que je t'ai conseillé de rajouter :P quoique je me demande si tu n'a pas intérêt à les garder pour avoir une requête plus rapide que celle-ci :-k

Tu n'avais pas posté dans la bonne rubrique (sql) donc tu n'a pas pu voir le message qui t'indiquait de poster le schéma te tes tables et donc mon conseil n'était pas nécessairement le plus adapté* puisqu'il faut que tu conserve ta deuxième table :wink:
* Valable aussi pour ce message.
Si je résouds le problème du DESC LIMIT, je pense que je garderai cette méthode, bien que celle-ci complique effectivement la tâche du serveur (le système d'incrémentation +1 / -1 directement dans la table "boite" aurait été tellement plus simple). Mais comme je l'avais précisé précédemment, je suis quoi qu'il en soit obligée de garder une 2e table, ne serait-ce que pour enregistrer l'ID du membre et ainsi l'empêcher de voter une 2e fois.


En tout cas, je vous remercie tous d'ores et déjà :wink: Les forums d'entre-aide, quels qu'ils soient, sont selon moi les plus belles réussites du Net :wink:

Liza
Invité n'ayant pas de compte PHPfrance

02 déc. 2007, 00:16

Et une fois de plus j'oublie de renseigner mon pseudo dans le post précédent. Veuillez m'en excuser :oops:

Liza.

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

02 déc. 2007, 00:20

Tu ferais mieux de t'inscrire, tu pourrais éditer tes messages et tu n'aurais plus à te soucier d'entrer un pseudo.

Sans oublier ça :
Je déplace... poste nous le schéma des tables pour qu'on puisse faire des tests

Liza
Invité n'ayant pas de compte PHPfrance

02 déc. 2007, 07:37

[Réglé]

Tu ferais mieux de t'inscrire, tu pourrais éditer tes messages et tu n'aurais plus à te soucier d'entrer un pseudo.
Effectivement. Le fait est que je ne veux pas que l'historique de mes messages ne comprenne uniquement des demandes d'aide :lol: Je pense qu'un forum marche dans les deux sens :wink: Mais c'est promis, si je dois poster un nouveau topic alors ce sera avec un vrai pseudo :wink:


J'ai finalement aboutit à ce que je souhaitais grâce à la dernière méthode que m'a proposé AB. Le problème du LIMIT venait d'une mauvais manip' avec ma boucle mais ça y est c'est réglé.


Voici le code final. Pour ceux qui m'ont aidé, et celà pourra peut-être aussi aider d'autres personnes. En tout cas grâce à vous et notamment AB, je sais maintenant comment faire une jointure dans un SELECT :wink: Je devrais veiller à l'avenir à ce que mon code soit propre.
$i=0;

$sql="SELECT COUNT(boite_op_rep) as c, boite_id, boite_titre  
FROM boite 
LEFT JOIN  boite_op  
ON boite_op_ididee = boite_id 
WHERE boite_op_rep = 1 
GROUP BY boite_id  
ORDER BY c DESC 
LIMIT 10";

$query=mysql_query($sql);
while($data = mysql_fetch_array($query))
{

    $i++;

    echo "<span class='txt_black_bold'>&nbsp;•<a class='txt_black_bold' title='".count['boite_titre']."' href='index.php?c=boiteaidees&view=zoom&id=".$data['boite_id']."'><span class='$couleur'>[$i]</span> ".substr($data['boite_titre'],0,30)."[...]</a></span><br /><span class='txt_black'>&nbsp;Opinions favorables : <b>".$data['0']."</b></span><br />";
}
Et voici la shéma de mes tables :

Code : Tout sélectionner

-- Version du serveur: 4.1.9 CREATE TABLE boite ( boite_id int(11) unsigned NOT NULL auto_increment, boite_date int(11) NOT NULL default '0', boite_date_rss text NOT NULL, boite_date_update int(11) NOT NULL default '0', boite_date_last int(11) NOT NULL default '0', boite_titre text NOT NULL, boite_desc text NOT NULL, boite_auteur_id int(11) NOT NULL default '0', boite_rub text NOT NULL, boite_vu int(11) NOT NULL default '0', PRIMARY KEY (boite_id) ); CREATE TABLE boite_op ( boite_op_id int(11) unsigned NOT NULL auto_increment, boite_op_ididee int(11) NOT NULL default '0', boite_op_auteur_id int(11) NOT NULL default '0', boite_op_rep int(11) NOT NULL default '0', PRIMARY KEY (boite_op_id) );
AB : Il s'agit donc de la solution la mieux adaptée pour ce que je veux faire. D'ailleurs tu l'évoquais toi-même hier :
Du coup tu devrais pouvoir te passer des champs que je t'ai conseillé de rajouter :P quoique je me demande si tu n'a pas intérêt à les garder pour avoir une requête plus rapide que celle-ci :-k

Tu n'avais pas posté dans la bonne rubrique (sql) donc tu n'a pas pu voir le message qui t'indiquait de poster le schéma te tes tables et donc mon conseil n'était pas nécessairement le plus adapté* puisqu'il faut que tu conserve ta deuxième table :wink:
* Valable aussi pour ce message.

Je vous réitère tous mes remerciements concernant votre aide qui m'a été très précieuse et qui m'aidera à l'avenir. Un grand merci particulier à AB qui a su très vite cerner mon problème (on reconnait bien les experts en la matière 8-) ).


Au plaisir de vous lire,

Amicalement Liza.

Modérateur PHPfrance
Modérateur PHPfrance | 7636 Messages

02 déc. 2007, 13:23

Le schéma des tables devait surtout servir à trouver cette fameuse requête plus rapidement et non pas pour faire style :wink:

Modération :
Puisque ta question est résolue, j'ajoute le tag [Résolu]
pour indiquer aux personnes qui voudront consulter ce sujet qu'il contient une solution.

Tu peux réaliser cette opération toi-même
en cliquant sur le bouton Image qui s'affiche en haut à gauche de ce sujet
si tu as posté le 1er message en tant que membre (inscrit et identifié).

Alors... inscris-toi !!! ;)

/!\ Avant de poster se documenter et rechercher.
Qui ne sait pas rendre un service n'a pas le droit d'en demander.
MaBrute