2010-07-26 14 views
124

Nie jestem ekspertem w MySQL. Muszę poznać rangę klientów. Tutaj dodaję odpowiednie standardowe zapytanie SQL ANSI dla mojego wymagania. Proszę, pomóż mi przekonwertować go na MySQL.Funkcja rang w MySQL

SELECT RANK() OVER (PARTITION BY Gender ORDER BY Age) AS [Partition by Gender], 
    FirstName, 
    Age, 
    Gender 
FROM Person 

Czy istnieje jakaś funkcja pozwalająca ustalić pozycję w MySQL?

+4

odpowiedzi podane poniżej albo nie zrobić partycję lub wytworzeniem DENSE_RANK zamiast rangi. Połączone pytanie obejmuje tylko ROW_NUMBER(), który różni się od RANK(). Bezwstydne samopromocyjne rozwiązanie dla prawdziwej [funkcji analitycznej ROW_NUMBER(), RANK(), DESNSE_RANK()] (http://kennethxu.blogspot.com/2016/04/analytical-function-in-mysql-rownumber.html) w MySQL –

Odpowiedz

214

Jedną z możliwości jest użycie rankingową zmienną, taką jak:

SELECT first_name, 
      age, 
      gender, 
      @curRank := @curRank + 1 AS rank 
FROM  person p, (SELECT @curRank := 0) r 
ORDER BY age; 

(SELECT @curRank := 0) część pozwala zmiennej inicjalizacji bez potrzeby dodatkowego polecenia SET.

przypadek testowy:

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1)); 

INSERT INTO person VALUES (1, 'Bob', 25, 'M'); 
INSERT INTO person VALUES (2, 'Jane', 20, 'F'); 
INSERT INTO person VALUES (3, 'Jack', 30, 'M'); 
INSERT INTO person VALUES (4, 'Bill', 32, 'M'); 
INSERT INTO person VALUES (5, 'Nick', 22, 'M'); 
INSERT INTO person VALUES (6, 'Kathy', 18, 'F'); 
INSERT INTO person VALUES (7, 'Steve', 36, 'M'); 
INSERT INTO person VALUES (8, 'Anne', 25, 'F'); 

Wynik:

+------------+------+--------+------+ 
| first_name | age | gender | rank | 
+------------+------+--------+------+ 
| Kathy  | 18 | F  | 1 | 
| Jane  | 20 | F  | 2 | 
| Nick  | 22 | M  | 3 | 
| Bob  | 25 | M  | 4 | 
| Anne  | 25 | F  | 5 | 
| Jack  | 30 | M  | 6 | 
| Bill  | 32 | M  | 7 | 
| Steve  | 36 | M  | 8 | 
+------------+------+--------+------+ 
8 rows in set (0.02 sec) 
+39

+1 za przebiegłą inline inline, to piękna sztuczka. – Charles

+0

Doskonała odpowiedź. Dziękuję bardzo ... – abhis

+15

Czy on nie poprosił o partycję? Moje zrozumienie partycji jest takie, że zestaw wyników miałby osobną pozycję dla mężczyzn i kobiet. –

22

wariację wersji Daniela obliczenia percentyla wraz z rangi. Również dwie osoby z tymi samymi ocenami uzyskają tę samą rangę.

set @totalStudents = 0; 
select count(*) into @totalStudents from marksheets; 
SELECT id, score, @curRank := IF(@prevVal=score, @curRank, @studentNumber) AS rank, 
@percentile := IF(@prevVal=score, @percentile, (@totalStudents - @studentNumber + 1)/(@totalStudents)*100), 
@studentNumber := @studentNumber + 1 as studentNumber, 
@prevVal:=score 
FROM marksheets, (
SELECT @curRank :=0, @prevVal:=null, @studentNumber:=1, @percentile:=100 
) r 
ORDER BY score DESC 

Wyniki zapytania dotyczącego danych sample -

+----+-------+------+---------------+---------------+-----------------+ 
| id | score | rank | percentile | studentNumber | @prevVal:=score | 
+----+-------+------+---------------+---------------+-----------------+ 
| 10 | 98 | 1 | 100.000000000 |    2 |    98 | 
| 5 | 95 | 2 | 90.000000000 |    3 |    95 | 
| 6 | 91 | 3 | 80.000000000 |    4 |    91 | 
| 2 | 91 | 3 | 80.000000000 |    5 |    91 | 
| 8 | 90 | 5 | 60.000000000 |    6 |    90 | 
| 1 | 90 | 5 | 60.000000000 |    7 |    90 | 
| 9 | 84 | 7 | 40.000000000 |    8 |    84 | 
| 3 | 83 | 8 | 30.000000000 |    9 |    83 | 
| 4 | 72 | 9 | 20.000000000 |   10 |    72 | 
| 7 | 60 | 10 | 10.000000000 |   11 |    60 | 
+----+-------+------+---------------+---------------+-----------------+ 
+1

Mimo że nie jest to optymalne pod względem wydajności, jest niesamowite! – Gaspa79

5

@Sam, punkt jest doskonała w koncepcji, ale myślę, że zrozumiał, co docs MySQL mówią na wskazanej stronie - albo Nie rozumiem :-) - i chciałem tylko dodać to, aby jeśli ktoś poczuł się niekomfortowo z odpowiedzią @ Daniela, będzie bardziej uspokojony lub przynajmniej trochę głębszy.

Widzisz "@curRank: = @curRank + 1 AS rank" wewnątrz SELECT nie jest "jednym stwierdzeniem", jest to jedna "atomowa" część oświadczenia, więc powinno być bezpieczne.

Dokument, do którego się odwołuje, wyświetla przykłady, w których ta sama zmienna zdefiniowana przez użytkownika znajduje się w 2 (atomowych) częściach instrukcji, na przykład "SELECT @curRank, @curRank: = @curRank + 1 AS rank".

Można argumentować, że @curRank jest używany dwukrotnie w odpowiedzi @ Daniela: (1) "@curRank: = @curRank + 1 AS rank" i (2) "(SELECT @curRank: = 0) r" ale ponieważ drugie użycie jest częścią klauzuli FROM, jestem prawie pewien, że jest gwarantowane, że najpierw zostanie ocenione; zasadniczo czyniąc z niego drugie i poprzednie stwierdzenie.

W rzeczywistości na tej samej stronie dokumentów MySQL, do której się odwołujesz, zobaczysz to samo w komentarzach - może to być miejsce, w którym @Daniel je otrzymał; tak, wiem, że to są komentarze, ale to komentarze na oficjalnej stronie dokumentów i to niesie ze sobą trochę wagi.

42

Oto ogólne rozwiązanie, które sortuje tabelę na podstawie kolumny i przypisuje rangę; wiersze opaskami przypisano ten sam status (stosuje się dodatkową zmienną w tym celu)

SET @prev_value = NULL; 
SET @rank_count = 0; 
SELECT id, rank_column, CASE 
    WHEN @prev_value = rank_column THEN @rank_count 
    WHEN @prev_value := rank_column THEN @rank_count := @rank_count + 1 
END AS rank 
FROM rank_table 
ORDER BY rank_column 

Należy zauważyć, że istnieją dwie instrukcje przypisania w drugim WHEN punktu.Przykładowe dane:

CREATE TABLE rank_table(id INT, rank_column INT); 
INSERT INTO rank_table (id, rank_column) VALUES 
(1, 10), 
(2, 20), 
(3, 30), 
(4, 30), 
(5, 30), 
(6, 40), 
(7, 50), 
(8, 50), 
(9, 50); 

wyjściowa:

+------+-------------+------+ 
| id | rank_column | rank | 
+------+-------------+------+ 
| 1 |   10 | 1 | 
| 2 |   20 | 2 | 
| 3 |   30 | 3 | 
| 4 |   30 | 3 | 
| 5 |   30 | 3 | 
| 6 |   40 | 4 | 
| 7 |   50 | 5 | 
| 8 |   50 | 5 | 
| 9 |   50 | 5 | 
+------+-------------+------+ 

SQL Fiddle

+0

To rozwiązanie lub rozwiązanie Mukesh powinno być właściwym rozwiązaniem. Chociaż technicznie uważam, że rozwiązania obu was reprezentują [gęsty ranking] (http://msdn.microsoft.com/en-us/library/ms173825.aspx), a nie regularną rangę. Oto dobre wytłumaczenie różnic: http://www.sqlservercurry.com/2009/04/rank-vs-denserank-with-example-using.html. – modulitos

+0

perfecto .....,. – atjoshi

+0

Czy możesz również poinformować nas, jak powinien wyglądać kod .php? Próbowałem podążać, ale powyższy kod nie działa. Jak wprowadzić dane do formatu .php? – leegod

3

Jeśli chcesz rangi tylko jedną osobę, można wykonać następujące czynności:

SELECT COUNT(Age) + 1 
FROM PERSON 
WHERE(Age < age_to_rank) 

Ten ranking odpowiada Oracle RANK function (Gdzie, jeśli masz ludzi w tym samym wieku dostają taką samą rangę i ranking po tym jest niesekwencyjny).

Jest to trochę szybsze niż użycie jednego z powyższych rozwiązań w podzapytaniu i wybranie go, aby uzyskać ranking jednej osoby.

Może to być używane do oceniania wszystkich, ale jest wolniejsze niż powyższe rozwiązania.

SELECT 
    Age AS age_var, 
(
    SELECT COUNT(Age) + 1 
    FROM Person 
    WHERE (Age < age_var) 
) AS rank 
FROM Person 
+0

Może to być ** dużo ** wolniejsze niż powyższe rozwiązanie, gdy liczba wierszy w tabeli" Osoba "rośnie. To _O (n^2) _ vs _O (n) _ wolniej. – xmedeko

12

Połączenie odpowiedzi Daniela i Salmana. Jednak ranga nie da, ponieważ kontynuuje się sekwencję z powiązaniami. Zamiast tego pomija ranking do następnego. Maksymalnie zawsze osiągaj liczbę wierszy.

SELECT first_name, 
       age, 
       gender, 
       IF([email protected]_last_age,@curRank:[email protected],@curRank:[email protected]_sequence) AS rank, 
       @_sequence:[email protected]_sequence+1,@_last_age:=age 
    FROM  person p, (SELECT @curRank := 1, @_sequence:=1, @_last_age:=0) r 
    ORDER BY age; 

Schema i Test Case:

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1)); 

INSERT INTO person VALUES (1, 'Bob', 25, 'M'); 
INSERT INTO person VALUES (2, 'Jane', 20, 'F'); 
INSERT INTO person VALUES (3, 'Jack', 30, 'M'); 
INSERT INTO person VALUES (4, 'Bill', 32, 'M'); 
INSERT INTO person VALUES (5, 'Nick', 22, 'M'); 
INSERT INTO person VALUES (6, 'Kathy', 18, 'F'); 
INSERT INTO person VALUES (7, 'Steve', 36, 'M'); 
INSERT INTO person VALUES (8, 'Anne', 25, 'F'); 
INSERT INTO person VALUES (9, 'Kamal', 25, 'M'); 
INSERT INTO person VALUES (10, 'Saman', 32, 'M'); 

wyjściowa:

+------------+------+--------+------+--------------------------+-----------------+ 
| first_name | age | gender | rank | @_sequence:[email protected]_sequence+1 | @_last_age:=age | 
+------------+------+--------+------+--------------------------+-----------------+ 
| Kathy  | 18 | F  | 1 |      2 |    18 | 
| Jane  | 20 | F  | 2 |      3 |    20 | 
| Nick  | 22 | M  | 3 |      4 |    22 | 
| Kamal  | 25 | M  | 4 |      5 |    25 | 
| Anne  | 25 | F  | 4 |      6 |    25 | 
| Bob  | 25 | M  | 4 |      7 |    25 | 
| Jack  | 30 | M  | 7 |      8 |    30 | 
| Bill  | 32 | M  | 8 |      9 |    32 | 
| Saman  | 32 | M  | 8 |      10 |    32 | 
| Steve  | 36 | M  | 10 |      11 |    36 | 
+------------+------+--------+------+--------------------------+-----------------+ 
+0

Jestem nowy w MySQL, ale czy to rozwiązanie jest w porządku? W dokumentach MySQL jest napisane, że "kolejność oceny wyrażeń zawierających zmienne użytkownika jest niezdefiniowana". https://dev.mysql.com/doc/refman/5.7/en/user-variables.html – narduk

32

Chociaż najbardziej upvoted szeregach odpowiedź, to nie ma partycji można zrobić własny Dołącz uzyskać całość podzielona również na:

SELECT a.first_name, 
     a.age, 
     a.gender, 
     count(b.age)+1 as rank 
FROM person a left join person b on a.age>b.age and a.gender=b.gender 
group by a.first_name, 
     a.age, 
     a.gender 

Use Case

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1)); 

INSERT INTO person VALUES (1, 'Bob', 25, 'M'); 
INSERT INTO person VALUES (2, 'Jane', 20, 'F'); 
INSERT INTO person VALUES (3, 'Jack', 30, 'M'); 
INSERT INTO person VALUES (4, 'Bill', 32, 'M'); 
INSERT INTO person VALUES (5, 'Nick', 22, 'M'); 
INSERT INTO person VALUES (6, 'Kathy', 18, 'F'); 
INSERT INTO person VALUES (7, 'Steve', 36, 'M'); 
INSERT INTO person VALUES (8, 'Anne', 25, 'F'); 

Odpowiedź:

Bill 32 M 4 
Bob  25 M 2 
Jack 30 M 3 
Nick 22 M 1 
Steve 36 M 5 
Anne 25 F 3 
Jane 20 F 2 
Kathy 18 F 1 
+0

jest to wspaniała odpowiedź właśnie dlatego, że muszę zrobić ranking partycji. Dziękuję Panu! –

+0

Odpowiedź inspirowana przez ciebie http://dba.stackexchange.com/a/90013/56827 –

+0

IMO ma taką samą złożoność jak podselekcja w odpowiedzi @Sam Kidman: O (n^2). Ale nie wiem, czy da się to zrobić lepiej w MySQL. – xmedeko

2

Najbardziej proste rozwiązanie do przodu, aby ustalić rangę danej wartości jest policzyć liczbę wartości przed go. Załóżmy, że mamy następujące wartości:

10 20 30 30 30 40 
  • Wszystkie wartości 30 są uważane 3rd
  • Wszystkie wartości 40 są uważane 6-ty (ranga) lub 4-ty (zwarta pozycja)

Teraz b potwierdzenie pierwotnego pytania.Oto przykładowe dane, które są klasyfikowane jako opisano w OP (oczekiwane szeregi są dodawane po prawej):

+------+-----------+------+--------+ +------+------------+ 
| id | firstname | age | gender | | rank | dense_rank | 
+------+-----------+------+--------+ +------+------------+ 
| 11 | Emily  | 20 | F  | | 1 |   1 | 
| 3 | Grace  | 25 | F  | | 2 |   2 | 
| 20 | Jill  | 25 | F  | | 2 |   2 | 
| 10 | Megan  | 26 | F  | | 4 |   3 | 
| 8 | Lucy  | 27 | F  | | 5 |   4 | 
| 6 | Sarah  | 30 | F  | | 6 |   5 | 
| 9 | Zoe  | 30 | F  | | 6 |   5 | 
| 14 | Kate  | 35 | F  | | 8 |   6 | 
| 4 | Harry  | 20 | M  | | 1 |   1 | 
| 12 | Peter  | 20 | M  | | 1 |   1 | 
| 13 | John  | 21 | M  | | 3 |   2 | 
| 16 | Cole  | 25 | M  | | 4 |   3 | 
| 17 | Dennis | 27 | M  | | 5 |   4 | 
| 5 | Scott  | 30 | M  | | 6 |   5 | 
| 7 | Tony  | 30 | M  | | 6 |   5 | 
| 2 | Matt  | 31 | M  | | 8 |   6 | 
| 15 | James  | 32 | M  | | 9 |   7 | 
| 1 | Adams  | 33 | M  | | 10 |   8 | 
| 18 | Smith  | 35 | M  | | 11 |   9 | 
| 19 | Zack  | 35 | M  | | 11 |   9 | 
+------+-----------+------+--------+ +------+------------+ 

Aby obliczyć RANK() OVER (PARTITION BY Gender ORDER BY Age) dla Sarah, można użyć tej kwerendy:

SELECT COUNT(id) + 1 AS rank, COUNT(DISTINCT age) + 1 AS dense_rank 
FROM testdata 
WHERE gender = (SELECT gender FROM testdata WHERE id = 6) 
AND age < (SELECT age FROM testdata WHERE id = 6) 

+------+------------+ 
| rank | dense_rank | 
+------+------------+ 
| 6 |   5 | 
+------+------------+ 

Aby obliczyć RANK() OVER (PARTITION BY Gender ORDER BY Age) dla Wszystkie wierszy można użyć tej kwerendy:

SELECT testdata.id, COUNT(lesser.id) + 1 AS rank, COUNT(DISTINCT lesser.age) + 1 AS dense_rank 
FROM testdata 
LEFT JOIN testdata AS lesser ON lesser.age < testdata.age AND lesser.gender = testdata.gender 
GROUP BY testdata.id 

A oto wynik (połączone wartości dodanej od prawej):

+------+------+------------+ +-----------+-----+--------+ 
| id | rank | dense_rank | | firstname | age | gender | 
+------+------+------------+ +-----------+-----+--------+ 
| 11 | 1 |   1 | | Emily  | 20 | F  | 
| 3 | 2 |   2 | | Grace  | 25 | F  | 
| 20 | 2 |   2 | | Jill  | 25 | F  | 
| 10 | 4 |   3 | | Megan  | 26 | F  | 
| 8 | 5 |   4 | | Lucy  | 27 | F  | 
| 6 | 6 |   5 | | Sarah  | 30 | F  | 
| 9 | 6 |   5 | | Zoe  | 30 | F  | 
| 14 | 8 |   6 | | Kate  | 35 | F  | 
| 4 | 1 |   1 | | Harry  | 20 | M  | 
| 12 | 1 |   1 | | Peter  | 20 | M  | 
| 13 | 3 |   2 | | John  | 21 | M  | 
| 16 | 4 |   3 | | Cole  | 25 | M  | 
| 17 | 5 |   4 | | Dennis | 27 | M  | 
| 5 | 6 |   5 | | Scott  | 30 | M  | 
| 7 | 6 |   5 | | Tony  | 30 | M  | 
| 2 | 8 |   6 | | Matt  | 31 | M  | 
| 15 | 9 |   7 | | James  | 32 | M  | 
| 1 | 10 |   8 | | Adams  | 33 | M  | 
| 18 | 11 |   9 | | Smith  | 35 | M  | 
| 19 | 11 |   9 | | Zack  | 35 | M  | 
+------+------+------------+ +-----------+-----+--------+ 
Powiązane problemy