2009-02-10 6 views
22

J'ai une table comme suit:Comment effectuer le classement Groupés dans MySQL

ID_STUDENT | ID_CLASS | GRADE 
----------------------------- 
    1  | 1  | 90 
    1  | 2  | 80 
    2  | 1  | 99 
    3  | 1  | 80 
    4  | 1  | 70 
    5  | 2  | 78 
    6  | 2  | 90 
    6  | 3  | 50 
    7  | 3  | 90 

J'ai besoin de puis grouper, de trier et de les commander à donner:

ID_STUDENT | ID_CLASS | GRADE | RANK 
------------------------------------ 
    2  | 1  | 99 | 1 
    1  | 1  | 90 | 2 
    3  | 1  | 80 | 3 
    4  | 1  | 70 | 4 
    6  | 2  | 90 | 1 
    1  | 2  | 80 | 2 
    5  | 2  | 78 | 3 
    7  | 3  | 90 | 1 
    6  | 3  | 50 | 2 

Maintenant, je sais que vous peut utiliser une variable temp pour classer, like here, mais comment puis-je le faire pour un ensemble groupé? Merci pour tout aperçu!

+0

Je me demande si MySQL supporte la fonction de la fenêtre RANK(): http://en.wikipedia.org/wiki/Select_(SQL)#Limiting_result_rows –

Répondre

28
SELECT id_student, id_class, grade, 
    @student:=CASE WHEN @class <> id_class THEN 0 ELSE @student+1 END AS rn, 
    @class:=id_class AS clset 
FROM 
    (SELECT @student:= -1) s, 
    (SELECT @class:= -1) c, 
    (SELECT * 
    FROM mytable 
    ORDER BY id_class, id_student 
) t 

Cela fonctionne d'une manière très simple:

  1. requête initiale est ordonnée par id_class premier, id_student secondes.
  2. @student et @class sont initialisés à -1
  3. @class est utilisé pour tester si le jeu suivant est entré. Si la valeur précédente de id_class (qui est stockée dans @class) n'est pas égale à la valeur actuelle (qui est stockée dans id_class), le @student est remis à zéro. Sinon, il est incrémenté.
  4. @class est affecté avec la nouvelle valeur de id_class, et il sera utilisé dans le test de l'étape 3 à la ligne suivante.
+0

Il m'a donné une erreur sur "set". Je l'ai modifié un peu et je l'ai mis au travail. Je l'ai posté comme une réponse ci-dessous. Un moyen de l'optimiser? Aussi, pouvez-vous expliquer comment cela fonctionne? Merci pour l'aide! – achinda99

+2

Est-ce garanti pour fonctionner comme prévu? MySQL [documentation] (http://dev.mysql.com/doc/refman/5.0/en//user-variables.html) dit: "En règle générale, vous ne devriez jamais attribuer une valeur à une variable utilisateur et lire la valeur dans la même déclaration " –

+0

@YouvalBronicki: non, ce n'est pas. Par mesure de sécurité, vous devriez assigner '@ student' et' @ class' dans des instructions séparées et/ou envelopper tout dans une procédure stockée. Cependant, tous les frameworks ne prennent pas en charge la persistance de connexion et les procédures stockées. – Quassnoi

4

Modifié par le haut, cela fonctionne, mais il est plus complexe que ce que je pense qu'il doit être:

SELECT ID_STUDENT, ID_CLASS, GRADE, RANK 
FROM 
    (SELECT ID_STUDENT, ID_CLASS, GRADE, 
     @student:=CASE WHEN @class <> id_class THEN 1 ELSE @student+1 END AS RANK, 
     @class:=id_class AS CLASS 
    FROM 
     (SELECT @student:= 0) AS s, 
     (SELECT @class:= 0) AS c, 
     (SELECT * 
      FROM Students 
      ORDER BY ID_CLASS, GRADE DESC 
     ) AS temp 
    ) AS temp2 
1

je fait quelques recherches, trouvé this article de trouver cette solution:

SELECT S2.*, 
FIND_IN_SET(
S2.GRADE 
, (
SELECT GROUP_CONCAT(GRADE ORDER BY GRADE DESC) 
FROM Students S1 
WHERE S1.ID_CLASS = S2.ID_CLASS 
) 
) AS RANK 
FROM Students S2 ORDER BY ID_CLASS, GRADE DESC; 

Des idées sur quoi est le mieux?

+0

Mine on est mieux, bien sur :) Celui-ci effectuera une jointure avec une classe entière pour chaque rangée sélectionnée, c'est mauvais pour la performance. Sur les données réelles, vous remarquerez à peine toute différence, cependant. – Quassnoi

3
SELECT g1.student_id 
    , g1.class_id 
    , g1.grade 
    , COUNT(*) AS rank 
    FROM grades AS g1 
    JOIN grades AS g2 
    ON (g2.grade, g2.student_id) >= (g1.grade, g1.student_id) 
    AND g1.class_id = g2.class_id 
GROUP BY g1.student_id 
     , g1.class_id 
     , g1.grade 
ORDER BY g1.class_id 
     , rank 
; 

Résultat:

+------------+----------+-------+------+ 
| student_id | class_id | grade | rank | 
+------------+----------+-------+------+ 
|   2 |  1 | 99 | 1 | 
|   1 |  1 | 90 | 2 | 
|   3 |  1 | 80 | 3 | 
|   4 |  1 | 70 | 4 | 
|   6 |  2 | 90 | 1 | 
|   1 |  2 | 80 | 2 | 
|   5 |  2 | 78 | 3 | 
|   7 |  3 | 90 | 1 | 
|   6 |  3 | 50 | 2 | 
+------------+----------+-------+------+ 
11

Il y a un problème avec la solution de Quassnoi (marquée comme meilleure réponse).

J'ai la même problématique (c.-à simuler la fonction de fenêtre SQL dans MySQL) et moi pour mettre en œuvre la solution de Quassnoi, en utilisant des variables définies par l'utilisateur pour stocker la valeur précédente de la ligne ...

Mais, peut-être après une mise à jour MySQL ou peu importe, ma requête ne fonctionnait plus. C'est parce que l'ordre d'évaluation des champs dans SELECT n'est pas garanti. L'affectation @class peut être évaluée avant l'assignation @student, même si elle est placée après dans le SELECT.

C'est Mentionné dans la documentation MySQL comme suit:

En règle générale, vous ne devez jamais attribuer une valeur à une variable utilisateur et lisez la valeur dans la même déclaration. Vous pourriez obtenir les résultats que vous attendez, mais ce n'est pas garanti.L'ordre de évaluation pour les expressions impliquant des variables utilisateur est indéfini et peut changer en fonction des éléments contenus dans une déclaration donnée; en outre, cette commande n'est pas garantie d'être la même entre les versions du serveur MySQL.

source: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html

Enfin j'ai utilisé un truc comme ça pour être sûr d'assigner @class après l'avoir lu:

SELECT id_student, id_class, grade, 
    @student:=CASE WHEN @class <> id_class THEN concat(left(@class:=id_class, 0), 0) ELSE @student+1 END AS rn 
FROM 
    (SELECT @student:= -1) s, 
    (SELECT @class:= -1) c, 
    (SELECT * 
    FROM mytable 
    ORDER BY id_class, grade desc 
) t 

En utilisant la fonction gauche() est juste utilisé pour définir @ variable de classe. Ensuite, concaténez le résultat de left() (égal à NULL) pour que le résultat attendu soit transparent.

Pas très élégant mais ça marche!

Questions connexes