Emotion Wave Tech Blog

福岡にあるエモーションウェーブ株式会社のエンジニアが書いています。

SQLでブービー賞

f:id:devew:20191227115202j:plain photo credit: Great Beyond via photopin cc

弊社が今の所に引っ越す前は近くにボーリング場があったため、納会の時とかにボーリング大会が恒例でした。 極めてレベルが低い大会でしたがみんな社長賞を狙って白熱しておりました。 (私はレベルを下げていた一人です)

さて、ここにボーリング大会の成績表があります。 大会情報テーブル

データID 大会名 なまえ スコア
1 春大会 Aさん 120
2 春大会 Bさん 80
3 春大会 Cさん 100
4 春大会 Dさん 140
5 夏大会 Aさん 100
6 夏大会 Bさん 80
7 夏大会 Cさん 90
8 夏大会 Dさん 110
9 秋大会 Aさん 90
10 秋大会 Bさん 120
11 秋大会 Cさん 130
12 秋大会 Dさん 150
13 冬大会 Aさん 110
14 冬大会 Bさん 120
15 冬大会 Cさん 170
16 冬大会 Dさん 130

ここから各大会のブービー賞を知りたい。となったとき、どうすればいいのでしょうか?

最下位とか最上位ならMINとかMAXとかでGROUP BYすれば出来そうですが、下から2番目だけをとるには? ORDER BYで並べて2番めだけ取る?いやそれだと各大会ごとにしないといけないから面倒くさいぞ。

そんなときはRANK関数を使います。早速使ってみましょう。 RANKの引数であるover内にスコア昇順で指定しましょう。

SELECT データID
,大会名
,なまえ
,スコア
,RANK() over (order by スコア asc) AS 順位
FROM 大会成績
データID 大会名 なまえ スコア 順位
2 春大会 Bさん 80 1
6 夏大会 Bさん 80 1
7 夏大会 Cさん 90 3
9 秋大会 Aさん 90 3
3 春大会 Cさん 100 5
5 夏大会 Aさん 100 5
8 夏大会 Dさん 110 7
13 冬大会 Aさん 110 7
14 冬大会 Bさん 120 9
1 春大会 Aさん 120 9
10 秋大会 Bさん 120 9
11 秋大会 Cさん 130 12
16 冬大会 Dさん 130 12
4 春大会 Dさん 140 14
12 秋大会 Dさん 150 15
15 冬大会 Cさん 170 16

全大会通してですがスコア順に順位が付きましたね。 ちなみに同一スコアの場合は同じ順位となり、この表みたいに1位ふたりで次が3位となります。 だから実際は同一スコアの場合は若い人を優遇するとか決めておいたほうがいいでしょう。

このデータは年齢は無いので仮にデータIDも順位判定要素としてみます。

SELECT データID
,大会名
,なまえ
,スコア
,RANK() over (order by スコア,データID asc) AS 順位
FROM 大会成績
データID 大会名 なまえ スコア 順位
2 春大会 Bさん 80 1
6 夏大会 Bさん 80 2
7 夏大会 Cさん 90 3
9 秋大会 Aさん 90 4
3 春大会 Cさん 100 5
5 夏大会 Aさん 100 6
8 夏大会 Dさん 110 7
13 冬大会 Aさん 110 8
1 春大会 Aさん 120 9
10 秋大会 Bさん 120 10
14 冬大会 Bさん 120 11
11 秋大会 Cさん 130 12
16 冬大会 Dさん 130 13
4 春大会 Dさん 140 14
12 秋大会 Dさん 150 15
15 冬大会 Cさん 170 16

欠番なく順位が付きましたね。

でも欲しいのは大会ごとのブービー賞です。 大会単位で順位を判定するにはPARTITION BYを使います。 RANK() over (PARTITION BY まとめたい項目 order by 並べたい項目 asc) となります。

SELECT データID
,大会名
,なまえ
,スコア
,RANK() over (PARTITION BY 大会名 order by スコア asc) AS 順位
FROM 大会成績
データID 大会名 なまえ スコア 順位
2 春大会 Bさん 80 1
3 春大会 Cさん 100 2
1 春大会 Aさん 120 3
4 春大会 Dさん 140 4
6 夏大会 Bさん 80 1
7 夏大会 Cさん 90 2
5 夏大会 Aさん 100 3
8 夏大会 Dさん 110 4
9 秋大会 Aさん 90 1
10 秋大会 Bさん 120 2
11 秋大会 Cさん 130 3
12 秋大会 Dさん 150 4
13 冬大会 Aさん 110 1
14 冬大会 Bさん 120 2
16 冬大会 Dさん 130 3
15 冬大会 Cさん 170 4

おお、大会ごとの(下位からの)順位がわかりました!

では今のを副問合せにして順位が2位だけ絞ってみましょう。

SELECT temp.データID
,temp.大会名
,temp.なまえ
,temp.スコア
FROM
(
SELECT データID
,大会名
,なまえ
,スコア
,RANK() over (PARTITION BY 大会名 order by スコア asc) AS 順位
FROM 大会成績
) temp
WHERE
temp.順位 = 2
データID 大会名 なまえ スコア
3 春大会 Cさん 100
7 夏大会 Cさん 90
10 秋大会 Bさん 120
14 冬大会 Bさん 120

各大会のブービー賞です。 BさんCさんが独占していたんですねw

RANKとPARTITION BYが無ければプログラム側で何とかしないといけませんがこれなら一発。 便利ですね。 ちなみにこれが利用できるDBは限られています。 OracleSQLServerは使えますがMySQLは使えなかったりします。 DBを選択する要素として分析関数の充実もチェックしておいたほうがいいかも知れません。