Bonjour à tous,

Cela fait maintenant quelques jours que je cherche solution a mon problème.
Je vais de ce pas vous l'exprimer :

Mon but est de faire un listing de résultat de concours.
J'ai 4 tableaux,

  • un avec les editions
  • un avec le jury (different suivant les editions)
  • un avec les participants (de meme)
  • un avec les notes (un jury pour un participant)

Illustration (avec la premiere édition 0):

  • Listing editions

  • Listing jury

  • Listing participants

  • Listing notes

La requete sql que j'utilise est la suivante :

$sql_ed = "SELECT *
    FROM site_gybs g
    INNER JOIN site_gybs_jury j ON g.edition = j.edition
    INNER JOIN site_gybs_notes n ON j.edition = n.edition
    INNER JOIN site_gybs_participant p ON n.edition = p.edition";

Vous imaginez bien que ce n'est pas du tout ce qu'il faut. Ce résultat me donne une 50aine de fois l'edition '0' au lieu de l'afficher une fois comme souhaité.

J'ai tout essayé, "union" "join left" etc... bref. je dois être loin de la bonne technique.
J'attend l'avis de vos cortex professionnels !

Merci d'avance !

15 réponses


Typhon
Réponse acceptée

Peut être parce que ton délimiteur de participant est '</br>- ' et que tu essais ensuite de récupérer les 3 premiers avec le séparateur ', '.

Mais il y à un autre problème, puisse que tu ordonnes par notes des participants et non par leur moyenne.

Par exemple, si en base de données j'ai

Canonier - 16/20
f-50 - 14/20
Canonier - 18/20
f-50 - 14/20
Canonier - 2/20
f-50 - 14/20

le GROUP_CONCAT(DISTINCT n.participant ORDER BY note DESC SEPARATOR ', ') va d'abord ordonnées les participants comme cela

Canonier - 18/20
Canonier - 16/20
f-50 - 14/20
f-50 - 14/20
f-50 - 14/20
Canonier - 2/20

puis supprimer tous les doublons pour garder cela

Canonier - 18/20
f-50 - 14/20

Or si on regarde au niveau des moyennes on a

f-50 - 14/20
Canonier - 12/20

On devrait donc avoir le résultat dans l'autre sens, de plus le mot clé DISTINCT avec ORDER BY est très dangereux, mon exemple ci-dessus ne devrait même pas marché, car MySQL ne va pas savoir par quoi commencer, ordonner, puis prendre les valeurs distinctes, ou bien prendre les valeurs distinctes puis ordonner, dans le premier cas on tombe dans l'exemple que j'ai donné, mais dans le deuxième on obtiendrait

Canonier - 16/20
f-50 - 14/20

Encore un résultat différent, et toujours aussi faux au niveau de l'ordonnancement cherché sur les moyennes.
Il faut donc reprendre la requête que je t'avais donné à la base, qui regroupe bien les participants par moyenne, puis regroupé par édition (en faite sur la première requête que je t'avais donnée, je pensais que tu voulais obtenir un résultat par participant, et non par édition). Voilà donc comment je m'y prendrais

SELECT
    m.edition,
    AVG(m.moyenne) as moyenne_edition,
    SUBSTRING_INDEX(GROUP_CONCAT(m.participant SEPARATOR ' _ '), ' _ ', 3) as podium_participants,
    SUBSTRING_INDEX(GROUP_CONCAT(m.moyenne SEPARATOR ' _ '), ' _ ', 3) as podium_moyennes,
    SUBSTRING_INDEX(GROUP_CONCAT(m.notes SEPARATOR ' _ '), ' _ ', 3) as podium_notes,
    SUBSTRING_INDEX(GROUP_CONCAT(m.jures SEPARATOR ' _ '), ' _ ', 3) as podium_jures
FROM (
    SELECT AVG(n.note) as moyenne, n.participant, n.edition, GROUP_CONCAT(n.note SEPARATOR ', ') as notes, GROUP_CONCAT(n.jure SEPARATOR ', ') as jures
    FROM notes n
    GROUP BY n.participant, n.edition
    ORDER BY moyenne DESC
) m
GROUP BY m.edition

J'ai simplifié la requête pour ne pas avoir à reprendre tous les champs, normalement tu devrais obtenir (dans le cas de mon exemple)

edition - moyenne_edition - podium_participants - podium_moyennes - podium_notes - podium_jures
0 - 13 - f-50 _ Canonier - 14 _ 12 - 14, 14, 14 _ 16, 18, 2 - jure3, jure2, jure1 _ jure1, jure2, jure3

Typhon
Réponse acceptée

Tu vas finir par me faire rougir ^^ c'est juste qu'à force de pratiquer, ça devient logique et à force de combinaisons on arrive toujours à obtenir ce que l'on veut =)

Et bien pour afficher, je ne sais pas comment tu t'y prends, mais je vais te montrer un exemple et tu adapteras ;)

// On récupère les éditions
$req = mysql_query("la requête ici");
// On parcourt toutes les éditions
while($res = mysql_fetch_array($req)) {
    // Affichage de l'édition courante
    echo '<h1>Édition n°'.$res'edition'].'</h1>';
    // Décompositions des différents types de podiums
    $podiumParticipants = explode(' _ ', $res'podium_participants']);
    $podiumCompos = explode(' _ ', $res'podium_compo']);
    $podiumNotes = explode(' _ ', $res'podium_notes']);
    $podiumJures = explode(' _ ', $res'podium_jures']);
    // Parcours du podium
    foreach($podiumParticipants as $key=>$participant) {
        // Récupération des différents éléments à la même marche du podium
        $compo = $podiumCompos$key];
        $notes = $podiumNotes$key];
        $jures = $podiumJures$key];
        // Après tu peux gérer ton affichage comme tu le veux
        echo
            '<span class="extr">'.
            ' <a title="'.$compo.' par '.$participant.'" href="..."></a>'.
            '</span>';
        // Je te montre aussi la décomposition des notes du participant listées par des ", "
        $notes = explode(', ', $notes);
        $jures = explode(', ', $jures);
        // Affichage des notes une à une
        echo
            '<h2>Notes de '.$participant.'</h2>'.
            '<li>';
        foreach($notes as $key=>$note) {
            if($key == 0) {
                echo '<ul>Première note : '.$note.'/20, attribué par '.$jure$key].'</ul>';
            }
            else if($key == 1) {
                echo '<ul>Deuxième note : '.$note.'/20, attribué par '.$jure$key].'</ul>';
            }
            else if($key == 2) {
                echo '<ul>Troisième note : '.$note.'/20, attribué par '.$jure$key].'</ul>';
            }
            else {
                echo '<ul>'.($key + 1).'ième note : '.$note.'/20, attribué par '.$jure$key].'</ul>';
            }
        }
        echo '</li>';

    }
}
Canonier
Auteur

Petit Up ?

Bonjour,
Il te manque pas le WHERE ?
du genre WHERE g.edition = 0

Canonier
Auteur

En fait il est utile quand on a plusieurs editions, mais la dans la bdd je n'en ai encore qu'une, donc pas de changement qu'il soit présent ou non (j'ai testé, et je vais retester pour etre sur ^^).

(si vous voulez voir un peu ce que donne la page en question : http://race-end-velocity.com/?p=gybs)

Canonier
Auteur

Doublon:
Je pense que le probleme vient de mes tables.
Puisque je demande un listing de 0 à n de g.edition en prenant les element des autres tables quand g.edition = X.edition.
et le probleme c'est que il y en a plusieurs dans les autres tables.. d'où cet affichage foireux avec 983 fois le meme affichage x)

En bref il me faudrait une nouvelle structure de tableaux (i guess).

Une idée ?

Je précise que dans l'affichage je souhaite classer les participant par moyenne de note decroissant :D
histoire de pimenter le tout x)

SELECT e.edition, e.date, e.theme, e.nb_copper, p.nom, AVG(n.note) as moyenne, GROUP_CONCAT(n.jure SEPARATOR ', ') as liste_jures, GROUP_CONCAT(n.note SEPARATOR ', ') as liste_notes
FROM editions e
LEFT JOIN participants p ON p.edition = e.edition
LEFT JOIN notes n ON n.participant = p.nom
WHERE e.edition = 0 // Si tu retires le WHERE, tu auras toutes les éditions, même si c'est inutile dans ton cas pour le moment, je le précise quand même
GROUP BY n.participant
ORDER BY moyenne DESC;

Ceci ferait t'il ton affaire ? =)

Canonier
Auteur

Ok !!
Alors la tu m'apprend un beau truc : GROUP_CONCAT()

Est ce que avec cette fonction on peut modifier les valeurs dans la liste ?
ex : GROUP_CONCAT(n.jure SEPARATOR ', ') <!--donne La Fouine, Chax, Zepset, Meo-->
et je voudrais que ca donne pour chaque juré : <a href="http://lien.com/$edition$juré">Juré</a>

donc avec une réutilisation de la valeur a deux niveau (lien et nom) .
Possible ?

Sinon j'ai un peu modifier le code

SELECT e.edition as edition, e.date, e.theme, e.nb_copper, p.nom, AVG(n.note) as moyenne, COUNT(DISTINCT p.nom) as nb_user, GROUP_CONCAT(DISTINCT n.jure SEPARATOR '</a> - <a>') as liste_jures, GROUP_CONCAT(DISTINCT n.participant ORDER BY note DESC SEPARATOR '') as liste_participant, SUBSTRING_INDEX(GROUP_CONCAT(DISTINCT n.participant ORDER BY note DESC SEPARATOR '</br>- '),', ',3) as podium
FROM site_gybs e
LEFT JOIN site_gybs_participant p ON p.edition = e.edition
LEFT JOIN site_gybs_notes n ON n.participant = p.nom
GROUP BY e.edition
ORDER BY moyenne DESC

ton code affichait deux fois l'édition (et je ne sais trop comment j'ai reduit a un ^^)

On peut d'ailleurs remarquer que j'ai essayé de troller ma question du début :

GROUP_CONCAT(DISTINCT n.participant ORDER BY note DESC SEPARATOR '</br>- ') as podium

puisque j'affiche

echo '- '.$ed_data'podium'];

(au lieu de modifier la valeur de la liste, j'ai 'enrobé' chacune d'entre elles).

Canonier
Auteur

trop confus, ou impossible ?
Tel est la question (petit Up :D !)

Ça doit certainement être possible de le faire, mais ça devient un peu plus complexe, et ce n'est pas le but des requêtes SQL, elles sont là pour te permettre de récupérer des données, mais pas les mettre en formes, à toi de faire la mise en forme plus tard.

Je pense que le plus simple c'est de faire comme ça (en gardant le GROUP_CONCAT(n.jure SEPARATOR ', ') as liste_jures)

$jures = explode(', ', $ed_data'liste_jures']);
foreach($jures as $jure) {
    echo '<a href="http://lien.com/'.$edition.$jure.'">'.$jure.'</a>';
}
Canonier
Auteur

Je te nomme officiellement dieu Sql :D
Merci mille fois pour ton aide !

bon.. il reste un soucis :D
je n'arrive pas a limiter à trois l'affichage des noms des participants.

SUBSTRING_INDEX(GROUP_CONCAT(DISTINCT n.participant ORDER BY note DESC SEPARATOR '</br>- '),', ',3) as podium

ce code ne fonctionne pas.. et je ne trouve pas comment faire autrement.

Apres ça, j'ai fini de vous embêter x)

Canonier
Auteur

Wow !
Moi qui gallerai dessus justement.

J'avais bien remarqué que la moyenne n'était pas la bonne (et je comptais me démerder en faisant un update de moyenne des qu'un jury ajoutait une note (hum.... le code bien sale ^^).

Finalement tel le Zorro des temps modernes, tu arrives au poil pour me donner l'ultime solution.

Tout de même une question : dois-je encore ajouter en 'join left' les tableaux ? (participant, etc..)

Je test ton code tout de suite ;)

Edit : code nickel !!!!!
je vais test un join left!

Edit² : Bon.... en gros il faut que je récupère le thème, la date, le nom des compo de chaque participant (lié au participant, ca va de soit), et le nombre de participant, mais ca je suis capable d'y arriver tout seul je pense x)
Le join left donne un truc chelou.. qui ne ressemble a pas grand chose, si ce n'est rien ..

Edit3 : "jamas deux sans trois ^^"
Finalement le join left était mal placé (boulet que je suis) donc c'est gagné, il ne me reste plus qu'a lié le nom de la compo de chaque participant, au participant.. :)

Et bien les JOIN ne sont pas vraiment nécessaire pour la manière dont tu as construit ta base de données, néans moins voilà comment tu peux faire pour faire les jointures, si tu veux récupérer plus d'infos ;)

SELECT
    e.edition,
    e.date,
    ROUND(AVG(m.moyenne), 2) as moyenne_edition,
    SUBSTRING_INDEX(GROUP_CONCAT(m.participant SEPARATOR ' _ '), ' _ ', 3) as podium_participants,
    SUBSTRING_INDEX(GROUP_CONCAT(m.moyenne SEPARATOR ' _ '), ' _ ', 3) as podium_moyennes,
    SUBSTRING_INDEX(GROUP_CONCAT(m.notes SEPARATOR ' _ '), ' _ ', 3) as podium_notes,
    SUBSTRING_INDEX(GROUP_CONCAT(m.jures SEPARATOR ' _ '), ' _ ', 3) as podium_jures,
    SUBSTRING_INDEX(GROUP_CONCAT(m.nom_compo SEPARATOR ' _ '), ' _ ', 3) as podium_compo
FROM (
    SELECT ROUND(AVG(n.note), 2) as moyenne, n.participant, n.edition, GROUP_CONCAT(n.note SEPARATOR ', ') as notes, GROUP_CONCAT(n.jure SEPARATOR ', ') as jures, p.nom_compo
    FROM notes n
    LEFT JOIN participants p ON p.edition = n.edition AND p.nom = n.participant
    GROUP BY n.participant, n.edition
    ORDER BY moyenne DESC
) m
RIGHT JOIN editions e ON m.edition = e.edition
GROUP BY m.edition

Simplement, tout ce qui touche à l'édition directement, je fais ma jointure dans le SELECT principale, et tout ce qui touche aux participants je fais ma jointure dans la sous requête (au passage un petit round sur les moyenne pour un affichage plus propre)

Pour ce qui est du LEFT ou RIGHT JOIN, je regarde qu'elle est ma table principale dans chaque requête, pour le SELECT principale, j’estime que c'est ma table éditions qui est la plus importante, je fais donc un RIGHT JOIN car ma table éditions est à droite du mot clé JOIN, pour la sous requête, j’estime que la table notes est plus importante que la table participants, je fais donc un LEFT JOIN car ma table notes se trouve à gauche du mot clé JOIN.
C'est un petit moyen mémo-technique que j'utilise pour les LEFT et RIGHT JOIN, le LEFT et RIGHT sont bien souvent facultatif, une jointure classique pourrait aussi bien marché, mais j'ai pris l'habitude d'utilisé ces jointures, qui permettent de récupérer tous les résultats de ma table principale, même si je n'ai pas de correspondance dans ma table principale avec les autres tables.

Par exemple dans ton cas, si tu as une édition "1" mais aucun participants ou notes pour cette édition, alors avec un RIGHT JOIN, dans tes résultats tu auras bien l'édition "1" avec la valeur NULL pour toutes les autres colonnes qui n'ont pas eut de correspondance avec l'édition "1", si tu avais fais un LEFT JOIN la ligne avec l'édition "1" n'aurait pas du tout été ressortie. Je pense qu'il est toujours préférable d'opter pour ce système de jointure, on peut vite remarquer les incohérences en base de données, et cela nous évite de perdre des données à l'affichage, en nous forçant à tenir la base de données propre ;)

Canonier
Auteur

Que la lumière soit!, dit Typhon.
Et la lumière fut faite sur le RIGHT et LEFT JOIN ! ^^

Mais comment ensuite dans le foreach je peux afficher le participant et sa compo

foreach($participants as $participant) {
    echo '<span class="extr">
        <a title="'.$ed_data'compo'].' par '.$participant.'" href="..."></a>
    </span> ';
}

Bon le code est plein d'inutilités que je vous épargne.. ce qui reste sert donc d'illustration.
(c'est ce code qui affiche les résultat en img sur mon site ( http://race-end-velocity.com/?p=gybs -click sur le + dans 'Dernières Editions')

J'aimerais mettre en 'title' sur les img : "$compo par $participant"
Le problème c'est qu'avec ton système, il classe les compo dans l'ordre des participants, mais ne les relie pas ensemble.
Et le résultat est que tout les participants ont le même nom de compo .. ^^

Chieur un jour, chieur toujours, à vous les studios :)

Canonier
Auteur

Merci Pour l'astuce de la key, j'aurais du y penser moi meme ^^

Bon aller j'en ai fini de t'embêter.. (ou pas, mais pour le moment si !!)
C'est parti pour les bonnes réponses
:D

Merci milles fois encore !!