2009-02-10 14 views
22

quindi ho una tabella come segue:Come eseguire classifica raggruppati in 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 

ho bisogno di allora gruppo, ordinare e li fine di dare:

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 

Ora so che tu possibile utilizzare una variabile temporanea per classificare, like here, ma come faccio per un gruppo impostato? Grazie per qualsiasi intuizione!

+0

Mi chiedo se MySQL supporta la funzione della finestra RANK(): http://en.wikipedia.org/wiki/Select_(SQL)#Limiting_result_rows –

risposta

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 

questo funziona in modo molto semplice:

  1. query iniziale viene ordinata da id_class prima, id_student secondo.
  2. @student e @class sono inizializzate a -1
  3. @class viene utilizzato per verificare se viene inserito il set successivo. Se il valore precedente di id_class (che è memorizzato in @class) non è uguale al valore corrente (che è memorizzato in id_class), lo @student viene azzerato. Altrimenti viene incrementato.
  4. @class viene assegnato con il nuovo valore id_class e verrà utilizzato nel test del passaggio 3 nella riga successiva.
+0

Mi ha dato un errore su "set". L'ho modificato un po 'e l'ho fatto funzionare. L'ho postato come risposta qui sotto. Qualche modo per ottimizzarlo? Inoltre, puoi spiegare come funziona? Grazie per l'aiuto! – achinda99

+2

È garantito che funzioni come previsto? MySQL [documentazione] (http://dev.mysql.com/doc/refman/5.0/en//user-variables.html) dice: "Come regola generale, non devi mai assegnare un valore a una variabile utente e leggere il valore all'interno della stessa frase " –

+0

@YouvalBronicki: no, non lo è. Per sicurezza, dovresti assegnare '@ student' e' @ class' in istruzioni separate e/o avvolgere tutto in una stored procedure. Tuttavia, non tutti i framework supportano la persistenza della connessione e le stored procedure. – Quassnoi

4

Modificato dall'alto, questo funziona ma la sua più complessa di quanto penso che deve essere:

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

ho fatto qualche ricerca, ha trovato this article a venire con questa soluzione:

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; 

Qualche idea su quale è meglio?

+0

Il mio è migliore, ovviamente :) Questo eseguirà un join con un'intera classe per ogni riga selezionata, ciò non è positivo per le prestazioni. Su dati reali difficilmente noterete alcuna differenza, però. – 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 
; 

Risultato:

+------------+----------+-------+------+ 
| 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

C'è un problema con la soluzione di Quassnoi (contrassegnato come migliore risposta).

Ho la stessa problematica (cioè la simulazione di funzioni SQL Finestra in MySQL) e ho usato per implementare la soluzione di Quassnoi, utilizzando variabili definite dall'utente per memorizzare precedente valore di riga ...

Ma, forse, dopo un upgrade di MySQL o qualsiasi altra cosa, la mia query non ha funzionato più. Questo perché l'ordine di valutazione dei campi in SELECT non è garantito. L'assegnazione @class potrebbe essere valutata prima dell'assegnazione @student, anche se è stata inserita dopo nella SELECT.

Questo è menzionate nelle documentazione di MySQL come segue:

Come regola generale, si dovrebbe mai assegnare un valore a una variabile utente e leggere il valore all'interno della stessa istruzione. Potresti ottenere i risultati che ti aspetti, ma questo non è garantito.L'ordine della valutazione per le espressioni che coinvolgono variabili utente non è definito e può cambiare in base agli elementi contenuti in una determinata istruzione; inoltre, questo ordine non è garantito lo stesso tra le versioni di MySQL Server.

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

Infine ho usato un trucco del genere per essere sicuri di assegnare @class dopo averlo letto:

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 

Utilizzando sinistra function() è solo utilizzato per impostare @ variabile di classe. Quindi, concatena il risultato di left() (uguale a NULL) al risultato previsto è trasparente.

Non molto elegante ma funziona!

Problemi correlati