2009-02-10 13 views
22

Mam więc następująco tabelę:Jak wykonać zgrupowanych rankingu w 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 

muszę wtedy grupa, rodzaj i kolejność ich dać:

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 

Teraz wiem, że ciebie może używać zmiennej tymczasowej do rangowania, like here, ale jak mogę to zrobić dla zgrupowanego zestawu? Dzięki za wszelki wgląd!

+0

Zastanawiam się, czy MySQL obsługuje funkcję okna RANK(): http://en.wikipedia.org/wiki/Select_(SQL)#Limiting_result_rows –

Odpowiedz

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 

Działa to w bardzo zwykły sposób:

  1. zapytania początkowe są sortowane według id_class pierwszy id_student sekundę.
  2. @student i @class są inicjowane -1
  3. @class służy do sprawdzania, czy następny zestaw jest wprowadzany. Jeśli poprzednia wartość parametru id_class (przechowywana w @class) nie jest równa wartości bieżącej (przechowywanej w id_class), wartość @student zostanie wyzerowana. W przeciwnym razie jest zwiększany.
  4. @class jest przypisana z nową wartością id_class i będzie używana w teście na etapie 3 w następnym wierszu.
+0

Dało mi to błąd w "ustawieniu". Zmodyfikowałem go trochę i zmusiłem do działania. Opublikowaliśmy to jako odpowiedź poniżej. Jakikolwiek sposób na optymalizację? Czy możesz wyjaśnić, jak to działa? Dzięki za pomoc! – achinda99

+2

Czy to gwarantuje działanie zgodnie z oczekiwaniami? MySQL [documentation] (http://dev.mysql.com/doc/refman/5.0/en//user-variables.html) mówi: "Zasadniczo nigdy nie powinieneś przypisywać wartości zmiennej użytkownika i czytać wartość w tym samym komunikacie " –

+0

@YouvalBronicki: nie, nie jest. Aby być po bezpiecznej stronie, należy przypisać '@ student' i' @ class' w oddzielnych instrukcjach i/lub zawijać wszystko do procedury przechowywanej. Jednak nie wszystkie frameworki obsługują utrzymywanie połączenia i procedury przechowywane. – Quassnoi

4

Modified z góry, to działa, ale jest to bardziej skomplikowane niż myślę, że musi być:

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

zrobiłem kilka wyszukiwania, uznał this article wymyślić tego rozwiązania:

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; 

Jakieś przemyślenia, które są lepsze?

+0

Oczywiście moja jest lepsza :) Ten wykona połączenie z całą klasą dla każdego wybranego wiersza, co jest złe dla wydajności. Na prawdziwych danych raczej nie zauważysz żadnej różnicy. – 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 
; 

Wynik:

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

Jest problem z roztworem Quassnoi użytkownika (oznaczone jako najlepszej odpowiedzi).

Mam ten sam problematyczny (tj symulowanie funkcji SQL okno w MySQL) i użyłem do wdrożenia rozwiązanie Quassnoi, wykorzystując zmienne zdefiniowane przez użytkownika do przechowywania poprzednią wartość wiersza ...

Ale może po aktualizacji MySQL lub cokolwiek, moje zapytanie już nie działało. Wynika to z faktu, że kolejność oceny pól w SELECT nie jest gwarantowana. Przypisanie klasy @ może zostać ocenione przed @student assignment, nawet jeśli zostanie umieszczone po SELECT.

to wymieniono w dokumentacji MySQL następująco:

Jako ogólną zasadę należy nigdy przypisać wartość do zmiennej użytkownika i odczytać wartość w tym samym komunikacie. Możesz uzyskać oczekiwane wyniki , ale nie jest to gwarantowane.Kolejność oceny dla wyrażeń obejmujących zmienne użytkownika jest niezdefiniowana i może zmieniać się w zależności od elementów zawartych w danej instrukcji; ponadto, nie można zagwarantować, że ta kolejność między wersjami serwera MySQL będzie niedostępna dla wersji .

źródło: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html

Wreszcie Użyłem trick takiego mieć pewność, aby przypisać @class po przeczytaniu go:

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 

Korzystanie lewo() jest właśnie używany do ustawiania @ zmienna klasy. Następnie połącz wynik lewy() (równy NULL), aby oczekiwany wynik był przezroczysty.

Niezbyt elegancki, ale działa!

Powiązane problemy