MySQLのランク関数


155

顧客のランクを調べる必要があります。ここで、対応するANSI標準SQLクエリを要件に追加しています。MySQLに変換するのを手伝ってください。

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

MySQLでランクを調べる関数はありますか?

回答:


266

1つのオプションは、次のようなランキング変数を使用することです。

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

この(SELECT @curRank := 0)パーツは、個別のSETコマンドを必要とせずに変数の初期化を可能にします。

テストケース:

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

結果:

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

52
不正なインライン初期化のための+1、それは美しいトリックです。
Charles

28
彼はパーティションを要求しませんでしたか?パーティションについての私の理解は、結果セットには男性と女性の別々のランキングがあるということです。
ジェシーディロン2010

2
@ジェシー:その場合、私は最近同様の質問に答えました:stackoverflow.com/questions/3162389/multiple-ranks-in-one-table
Daniel Vassallo

6
アンとボブの両方に4のランクを付けたい場合はどうなりますか?
Fahim Parkar 2012年

8
partition by gender分析関数の一部(全体の結果ではなく性別ごとのランク値に「番号を付ける」)がないため、これは質問の例を実装していません
a_horse_with_no_name

53

以下は、パーティションの密なランクを行に割り当てる一般的なソリューションです。ユーザー変数を使用します。

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    |

db <> fiddleのデモ


2
このソリューション、またはMukeshのソリューションは、正しいソリューションである必要があります。技術的には私はあなたたち2人の解決策は通常のランクではなく密度の高いランキングを表すと信じています。ここに違いの良い説明があります:sqlservercurry.com/2009/04/…
modulitos 2014年

.phpコードの正確な状態を教えてください。フォローしようとしましたが、上記のコードは機能しません。.php形式への入力方法は?
作成者

このソリューションはあまり一般的ではありません。rank_columnの値が0の場合は機能しません。sqlfiddle.com/#!2 / 9c5dd / 1
マイク

1
:CASE文のELSE節追加@マイクELSE @rank_count := @rank_count + 1
プリンスOdame

1
@abhash ORDER BY gender, age DESC
Salman A

52

最も賛成された回答はランク付けされますが、分割されません。自己結合を実行して、全体を分割することもできます。

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

パーティションランキングを行う必要があるため、これはすばらしい答えです。ありがとうございます!
キムスタック

IMOは、@ Sam Kidmanの回答の副選択と同じ複雑さを持っています:O(n ^ 2)。しかし、MySQLでもっとうまくできるかどうかはわかりません。
xmedeko 2017年

onlamp.com/pub/a/mysql/2007/03/29/…をチェックして、同じ線に沿ったすばらしいチュートリアルを見つけてください
ferics2

自己参加してランクを獲得しましょう!それは素晴らしいことです。最後に、変数MySQL 8ウィンドウ関数のないソリューション。:)
Timo

24

ランクとともにパーセンタイルを計算するためのダニエルのバージョンの微調整。また、同じマークの2人は同じランクになります。

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

サンプルデータのクエリの結果-

+----+-------+------+---------------+---------------+-----------------+
| 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
これは実際には最適なパフォーマンスではありませんが、素晴らしいです!
Gaspa79

18

ダニエルとサルマンの答えの組み合わせ。ただし、同順位の連続が存在するため、ランクは与えられません。代わりに、ランクを次にスキップします。したがって、最大数は常に行数に達します。

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

1
MySQLは初めてですが、このソリューションで大丈夫ですか?MySQLのドキュメントでは、「ユーザー変数を含む式の評価順序は定義されていません」と述べています。dev.mysql.com/doc/refman/5.7/en/user-variables.html
narduk

13

MySQL 8以降、MySQLでもウィンドウ関数を使用できるようになりました。https//dev.mysql.com/doc/refman/8.0/en/window-functions.html

クエリはまったく同じ方法で記述できます。

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

SQLの古いバージョンでは機能しないのは間違いありません。それに彼の質問のコピーと過去のようなものだったので、それが答えに合うようには感じられません。
newdark-it 2018

4
@ brand-it MySQL 8以降のユーザーの場合、ランクが利用可能になったことを知らせるため、この回答は重要です。ここまでスクロールしていなければ、以前の回答が唯一の解決策だったと思います。
Steve Smith

1
@SteveSmith良い点は、新しいバージョンのMYSQLを使用している人たちにこの答えがあることは素晴らしいことです。
newdark-it

はい、ユーザー変数とロジックブロックに関する多くの回答に失望しています。MySQLの新しいバージョンでは、パーティションによる組み込みのグループ化を提供するRANK()関数を使用して、非常に簡単に行うことができます。
James Bond

5

@サム、あなたのポイントはコンセプトが優れていますが、参照ページでMySQLドキュメントが言っていることを誤解していると思います-または私は誤解しています:-)-誰かが@に不快に感じる場合にこれを追加したかっただけですダニエルの答えは、彼らはより安心するか、少なくとももう少し深く掘り下げるでしょう。

あなたは"@curRank := @curRank + 1 AS rank"中を見ますSELECTそれが安全である必要がありますので、それは声明の一つ「原子」の部分だ、「1つの文」ではありません。

参照するドキュメントは、ステートメントの2(アトミック)部分に同じユーザー定義変数が含まれている例を示しています。たとえば、 "SELECT @curRank, @curRank := @curRank + 1 AS rank"ます(。

@curRank@Danielの答えで2回使用されていると主張する人もいるでしょう:(1)"@curRank := @curRank + 1 AS rank"と(2)"(SELECT @curRank := 0) r"が、2番目の使用法はFROM句の、最初に評価されることが保証されています。本質的にそれを2番目の前のステートメントにします。

実際、あなたが参照した同じMySQLドキュメントページでは、コメントに同じソリューションが表示されます。@ Danielがそれを取得した場所である可能性があります。ええ、私はそれがコメントであることを知っていますが、それは公式のドキュメントページのコメントであり、それはある程度の重みを持っています。


これはドキュメントによって正当化されていません。これは単なる(ファジー)推測です。マニュアルには明示的に定義されていない、同じ変数の使用と書き込みのすべての回答と同様に、マニュアルには期待どおりに機能する可能性のあるテキストがたくさんありますが、期待していると思うものや使用法については述べていません非保証動作の説明です。PS SETの外部での8.0の変数割り当ては非推奨です。
philipxy 2018年

4

特定の値のランクを決定する最も簡単な解決策は、そのの値の数を数えることです。次の値があるとします。

10 20 30 30 30 40
  • すべての30値が考慮されます 3番目
  • すべての40値は6番目(ランク)または4番目(密ランク)と見なされます

元の質問に戻ります。OPで説明されているようにソートされたいくつかのサンプルデータを次に示します(期待されるランクが右側に追加されます)。

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

Sarahを計算するに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

そしてここに結果があります(結合された値が右側に追加されます):

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

3

1人だけをランク付けしたい場合は、以下を実行できます。

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

このランキングは、オラクルのRANK関数に対応しています(同じ年齢の人がいる場合、同じランクが得られ、その後のランキングは連続しません)。

上記のソリューションの1つをサブクエリで使用し、そこから選択して1人のランキングを取得するよりも少し高速です。

これは全員をランク付けするために使用できますが、上記のソリューションよりも低速です。

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

テーブルの行数が増えると、上記のソリューションよりもはるかに遅くなる可能性がありPersonます。それはですO(N ^ 2)O(n)と遅くなります。
xmedeko 2017年

2

ダニエルの回答とサルマンの回答を組み合わせたエランダックの回答の「ただし」を回避するには、次の「パーティションの回避策」のいずれかを使用できます。

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により、結果と同様のデータ構造が得られます。例として、以下を参照してください。特に、partitionSequenceは、このメソッドを使用して、新しいpartitionRankごとに常に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
...

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');
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.