时间:2010-07-26 09:35:48

标签: mysql sql rank

11 个答案:

答案 0 :(得分:248)

答案 1 :(得分:50)

这是一个通用的解决方案,它将分区上的密集排名分配给行。它使用用户变量:

CREATE TABLE person (
    id INT NOT NULL PRIMARY KEY,
    firstname VARCHAR(10),
    gender VARCHAR(1),
    age INT
);

INSERT INTO person (id, firstname, gender, age) VALUES
(1,  'Adams',  'M', 33),
(2,  'Matt',   'M', 31),
(3,  'Grace',  'F', 25),
(4,  'Harry',  'M', 20),
(5,  'Scott',  'M', 30),
(6,  'Sarah',  'F', 30),
(7,  'Tony',   'M', 30),
(8,  'Lucy',   'F', 27),
(9,  'Zoe',    'F', 30),
(10, 'Megan',  'F', 26),
(11, 'Emily',  'F', 20),
(12, 'Peter',  'M', 20),
(13, 'John',   'M', 21),
(14, 'Kate',   'F', 35),
(15, 'James',  'M', 32),
(16, 'Cole',   'M', 25),
(17, 'Dennis', 'M', 27),
(18, 'Smith',  'M', 35),
(19, 'Zack',   'M', 35),
(20, 'Jill',   'F', 25);

SELECT person.*, @rank := CASE
    WHEN @partval = gender AND @rankval = age THEN @rank
    WHEN @partval = gender AND (@rankval := age) IS NOT NULL THEN @rank + 1
    WHEN (@partval := gender) IS NOT NULL AND (@rankval := age) IS NOT NULL THEN 1
END AS rnk
FROM person, (SELECT @rank := NULL, @partval := NULL, @rankval := NULL) AS x
ORDER BY gender, age;

请注意,变量赋值放在CASE表达式中。这(理论上)负责评估问题的顺序。添加IS NOT NULL以处理数据类型转换和短路问题。

PS:通过删除检查平局的所有条件,可以很容易地将其转换为分区上的行号。

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

Demo on db<>fiddle

答案 2 :(得分:45)

虽然最受欢迎的答案排名,但它没有分区,你可以自己加入以使整个事情也被分区:

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

使用案例

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');

<强>答案

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

答案 3 :(得分:17)

丹尼尔和萨尔曼答案的结合。然而,等级不会给出存在关系的连续序列。相反,它会将排名跳到下一个。所以最大值总是达到行数。

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

架构和测试用例:

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');

输出:

+------------+------+--------+------+--------------------------+-----------------+
| first_name | age  | gender | rank | @_sequence:=@_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 |
+------------+------+--------+------+--------------------------+-----------------+

答案 4 :(得分:7)

Starting with MySQL 8, you can finally use window functions also in MySQL: https://dev.mysql.com/doc/refman/8.0/en/window-functions.html

Your query can be written exactly the same way:

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

答案 5 :(得分:5)

@Sam,您的观点非常出色,但我认为您误解了MySQL文档在引用页面上的内容 - 或者我误解了:-) - 我只想添加这个以便如果有人感到不舒服在@ Daniel的回答中,他们会更放心,或者至少深入挖掘一下。

你看到SELECT中的“@curRank:= @curRank + 1 AS等级”不是“一个语句”,它是语句中的一个“原子”部分所以它应该是安全的。

您引用的文档继续显示在语句的2(原子)部分中使用相同的用户定义变量的示例,例如“SELECT @curRank,@curRank:= @curRank + 1 AS rank”。< / p>

有人可能会说@curRank在@ Daniel的回答中使用了两次:(1)“@curRank:= @curRank + 1 AS等级”和(2)“(SELECT @curRank:= 0)r”但是由于第二次使用是FROM子句的一部分,我很确定它可以保证首先被评估;基本上使它成为第二个,也就是前面的陈述。

事实上,在您引用的同一个MySQL文档页面上,您会在评论中看到相同的解决方案 - 它可能是@Daniel从中得到的;是的,我知道这是评论,但它是官方文档页面上的评论,确实有一些重量。

答案 6 :(得分:4)

确定给定值的等级的最直接的解决方案是在之前计算值的数量。假设我们有以下值:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:lei="http://www.leiroc.org/data/schema/leidata/2014" version="1.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <lei:LEIData>
      <lei:LEIRecords>
        <xsl:apply-templates select="lei:LEIData/lei:LEIRecords/lei:LEIRecord"/>
      </lei:LEIRecords>
  </lei:LEIData>
</xsl:template>

  <xsl:template match="lei:LEIData/lei:LEIRecords/lei:LEIRecord">
    <lei:LEIRecord>
      <xsl:for-each select="
                    lei:LEI|lei:Entity/lei:LegalName|lei:Entity/lei:BusinessRegisterEntityID|
                    lei:Entity/lei:LegalJurisdiction|lei:Entity/lei:LegalForm|
                    lei:LegalForm/lei:EntityStatus|
                    lei:Registration/lei:InitialRegistrationDate|
                    lei:Registration/lei:LastUpdateDate|
                    lei:Registration/lei:RegistrationStatus|
                    lei:Registration/lei:NextRenewalDate|
                    lei:Registration/lei:ManagingLOU|
                    lei:Registration/lei:ValidationSources                   
                    ">
        <xsl:attribute name="{name()}">
          <xsl:value-of select="text()"/>
        </xsl:attribute>
      </xsl:for-each>
      <xsl:for-each select="
                    lei:Entity/lei:BusinessRegisterEntityID/@register
                    ">
        <xsl:attribute name="register">
          <xsl:value-of select="."/>
        </xsl:attribute>
      </xsl:for-each>
      <xsl:for-each select="
                    lei:Entity/lei:LegalAddress/lei:Line1|
                    lei:Entity/lei:LegalAddress/lei:City|
                    lei:Entity/lei:LegalAddress/lei:Country|
                    lei:Entity/lei:LegalAddress/lei:PostalCode                   
                    ">
          <xsl:attribute name="legaladdress_{local-name()}">
            <xsl:value-of select="text()"/>
          </xsl:attribute>
        </xsl:for-each>
      <xsl:for-each select="
                    lei:Entity/lei:HeadquartersAddress/lei:Line1|
                    lei:Entity/lei:HeadquartersAddress/lei:City|
                    lei:Entity/lei:HeadquartersAddress/lei:Country|
                    lei:Entity/lei:HeadquartersAddress/lei:PostalCode                   
                    ">
        <xsl:attribute name="headquarter_{local-name()}">
          <xsl:value-of select="text()"/>
        </xsl:attribute>
      </xsl:for-each>
    </lei:LEIRecord>
  </xsl:template>
</xsl:stylesheet>
  • 所有10 20 30 30 30 40 值都被视为第三
  • 所有30值均被视为第6个(排名)或第4个(密集排名)

现在回到原来的问题。以下是一些样本数据,按OP中的描述进行排序(预期排名在右侧添加):

40

要为 Sarah 计算+------+-----------+------+--------+ +------+------------+ | 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 | +------+-----------+------+--------+ +------+------------+ ,您可以使用此查询:

RANK() OVER (PARTITION BY Gender ORDER BY Age)

要为 所有 行计算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 | +------+------------+ ,您可以使用此查询:

RANK() OVER (PARTITION BY Gender ORDER BY Age)

这是结果(连接值在右侧添加):

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

答案 7 :(得分:3)

如果您只想对一个人进行排名,您可以执行以下操作:

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

这个排名对应于oracle RANK函数(如果你有相同年龄的人,他们得到相同的排名,之后的排名是非连续的)。

比在子查询中使用上述解决方案之一并从中进行选择以获得一个人的排名要快一点。

这可以用来对每个人进行排名,但它比上述解决方案慢。

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

答案 8 :(得分:2)

为避免Erandac的答案与Daniel和Salman的答案结合使用“ however”,可以使用以下“分区解决方法”之一

SELECT customerID, myDate

  -- partition ranking works only with CTE / from MySQL 8.0 on
  , RANK() OVER (PARTITION BY customerID ORDER BY dateFrom) AS rank, 

  -- Erandac's method in combination of Daniel's and Salman's
  -- count all items in sequence, maximum reaches row count.
  , IF(customerID=@_lastRank, @_curRank:=@_curRank, @_curRank:=@_sequence+1) AS sequenceRank
  , @_sequence:=@_sequence+1 as sequenceOverAll

  -- Dense partition ranking, works also with MySQL 5.7
  -- remember to set offset values in from clause
  , IF(customerID=@_lastRank, @_nxtRank:=@_nxtRank, @_nxtRank:=@_nxtRank+1 ) AS partitionRank
  , IF(customerID=@_lastRank, @_overPart:=@_overPart+1, @_overPart:=1 ) AS partitionSequence

  , @_lastRank:=customerID
FROM myCustomers, 
  (SELECT @_curRank:=0, @_sequence:=0, @_lastRank:=0, @_nxtRank:=0, @_overPart:=0 ) r
ORDER BY customerID, myDate

此代码段中第3个变体中的分区排名将返回连续的排名编号。这将导致类似于rank() over partition by结果的数据结构。例如,请参见下文。特别是,对于每个新的partitionRank, partitionSequence将始终以1开头,使用此方法:

customerID    myDate   sequenceRank (Erandac)
                          |    sequenceOverAll
                          |     |   partitionRank
                          |     |     | partitionSequence
                          |     |     |    | lastRank
... lines ommitted for clarity
40    09.11.2016 11:19    1     44    1   44    40
40    09.12.2016 12:08    1     45    1   45    40
40    09.12.2016 12:08    1     46    1   46    40
40    09.12.2016 12:11    1     47    1   47    40
40    09.12.2016 12:12    1     48    1   48    40
40    13.10.2017 16:31    1     49    1   49    40
40    15.10.2017 11:00    1     50    1   50    40
76    01.07.2015 00:24    51    51    2    1    76
77    04.08.2014 13:35    52    52    3    1    77
79    15.04.2015 20:25    53    53    4    1    79
79    24.04.2018 11:44    53    54    4    2    79
79    08.10.2018 17:37    53    55    4    3    79
117   09.07.2014 18:21    56    56    5    1   117
119   26.06.2014 13:55    57    57    6    1   119
119   02.03.2015 10:23    57    58    6    2   119
119   12.10.2015 10:16    57    59    6    3   119
119   08.04.2016 09:32    57    60    6    4   119
119   05.10.2016 12:41    57    61    6    5   119
119   05.10.2016 12:42    57    62    6    6   119
...

答案 9 :(得分:0)

select id,first_name,gender,age,
rank() over(partition by gender order by age) rank_g
from person

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,'AKSH',32,'M');

答案 10 :(得分:0)

sudo make install
  • 你可以使用 asc/desc 取决于你想要的顺序是升序还是降序