「あれ?GROUP BY
とMAX
で集計はできたけど…なんで他の列が取れないの?」
「部署ごとの最高給料が知りたいのに、誰の給料なのか分からない…」
SQLを勉強していると、グループごとに最大値を取りたいという場面に必ずと言っていいほど出くわします。
そして多くの人が最初につまずくのが、GROUP BY
とMAX()
を使ったときに「他の列が取れない問題」。
これは決してあなたの理解力が足りないわけではなく、SQLの仕様に根本的な理由があります。
この記事では、
を、初学者でも理解できるように丁寧に・絶対に誤解のないように解説していきます。
SQLはちょっとした「仕様のクセ」を知っているかどうかで、結果がまったく変わることがあります。
この記事を通して、「グループ化 + 最大値」の本質をしっかり押さえて、どんな場面でも使いこなせるようになりましょう!
基本:GROUP BY + MAX の使い方
まずは、いちばん基本的な使い方から確認していきましょう。
例えば、次のような「売上」テーブルがあるとします。
店舗名 | 売上日 | 売上金額 |
---|---|---|
東京店 | 2025-07-01 | 12000 |
大阪店 | 2025-07-01 | 9000 |
東京店 | 2025-07-02 | 15000 |
大阪店 | 2025-07-02 | 11000 |
このデータから、「各店舗で最も売上が高かった金額」を知りたいとき、基本的には以下のようなSQLを書きます。
SELECT 店舗名, MAX(売上金額)
FROM 売上
GROUP BY 店舗名;
このSQLの意味は、「店舗ごとにデータをグループ化し、それぞれのグループの中で最大の売上金額を出す」というものです。
実行結果は以下のようになります。
店舗名 | MAX(売上金額) |
---|---|
東京店 | 15000 |
大阪店 | 11000 |
これで「最大値」はちゃんと取れていますよね。ここまでは問題ありません。
でも、ここで疑問が出てきます・・・
「じゃあ、その最大売上がいつだったかを知りたい場合は?」
「どの行(レコード)が最大だったのか他の列も一緒に取りたいんだけど…」
残念ながら、GROUP BY
とMAX
だけではそれができません。
無理やりやろうとすると、間違ったデータが出てしまうこともあります。
ここが、最初の“つまずきポイント”なんです。
次の章では、この問題点を掘り下げて、「なぜ他の列が一緒に取れないのか?」をわかりやすく解説します。
他の列を一緒に取得したいとき
先ほどは、GROUP BY
とMAX
を使って「各店舗の最大売上金額」を取得しました。
でも、実務で本当に欲しいのは「その売上が何日だったのか」「誰が売ったのか」といった、“最大値に対応する行全体の情報”ですよね。
ここで、多くの人が間違った方向に進みがちです。
よくある間違い:MAXと他の列を一緒にSELECTする
以下のようなSQLを書いたこと、ありませんか?
SELECT 店舗名, 売上日, MAX(売上金額)
FROM 売上
GROUP BY 店舗名;
一見すると動いているように見えるかもしれません。
しかしこのSQL、危険です。
というのも、売上日
はGROUP BY
されていないし、MAXの対象でもない。
MySQLの設定によってはこのSQLを「おおめに見て」動かしてくれますが、本来は不正確な結果が返ってくる可能性があります。
なぜこの書き方ではダメなのか?
SQLのルールとして、GROUP BY
を使うときは、
- グループ化のキーとなる列(=
GROUP BY
に指定した列) - 集約関数(
MAX
,SUM
,AVG
, など)を使った列
のどちらかしかSELECT句に書いてはいけないのが原則です。
つまり、「どの売上日か」は1つに特定できないから、一緒に出してはいけないんですね。
じゃあどうすればいいの?
ここからがこの記事の本題です。
この「他の列も一緒に正しく取得したい」というニーズに応えるために、3つの正しい方法があります。
- サブクエリを使って最大値を特定し、その行を抽出する方法
- JOINを使って、最大値を持つ行と結びつける方法
- ウィンドウ関数(MySQL 8.0以降)を使って、最大値の行だけを選び出す方法
次のセクションからは、それぞれの方法を実行例つきで丁寧に解説していきます。
「これなら使いこなせそう!」と思えるやり方がきっと見つかるはずです。
解決法1:サブクエリで最大値を抽出
まずご紹介するのは、「サブクエリ」を使って最大値の行を正確に取得する方法です。
これは少しだけSQLの理解が必要ですが、MySQL 5.x以降で広く使える、非常に実用的なテクニックです。
サブクエリとは?
サブクエリとは、SQLの中に書く「別のSELECT文」のことです。
今回のように、「ある店舗の中で、最大の売上金額だけを取りたい」といったときに、その最大値を一度別で求めてから比較するような使い方ができます。
実際の例:最大売上金額と一致する行を取り出す
SELECT *
FROM 売上 AS s
WHERE 売上金額 = (
SELECT MAX(売上金額)
FROM 売上 AS s2
WHERE s2.店舗名 = s.店舗名
);
- 外側のSELECT(
s
)では、売上表から全ての列を見ています。 - 内側のサブクエリ(
s2
)は、「その店舗名における最大の売上金額」を取得します。 - 最後に、外側と内側の店舗名が一致していて、売上金額が最大値と等しい行だけを抽出しています。
実行結果
店舗名 | 売上日 | 売上金額 |
---|---|---|
東京店 | 2025-07-02 | 15000 |
大阪店 | 2025-07-02 | 11000 |
これで「最大値を持つ業そのもの」を、安全かつ正確に取得することができました。
サブクエリのメリットと注意点
メリット
- 実務での安定感バツグン。どのMySQLバージョンでも使える。
- 1行1行を比較して取得するので、精度が高い。
注意点
- テーブルが大きいと、ややパフォーマンスに影響が出る可能性があります。
※(インデックスの有無にもよります)
この方法が理解できれば、「サブクエリは怖くない!」と思ってもらえるはずです。
解決法2:JOINで最大値を結合
サブクエリの方法に続いて、今度は「JOIN」を使って、最大値を持つ行を取り出す方法をご紹介します。
JOINは「別のテーブル(またはサブクエリ)と結びつける」機能ですが、
うまく使えば 「最大値の行だけをピンポイントで抽出」するのにも使えるんです。
実際の例:最大値サブクエリとJOINする
SELECT s.*
FROM 売上 s
JOIN (
SELECT 店舗名, MAX(売上金額) AS 最大売上
FROM 売上
GROUP BY 店舗名
) m
ON s.店舗名 = m.店舗名 AND s.売上金額 = m.最大売上;
- サブクエリ(
m
)で「各店舗の最大売上金額」を取得します。 - それを、元の売上テーブル(
s
)と店舗名と売上金額の両方で結びつける。 - 結果として、「最大値に一致する行」だけが残るという仕組みです。
実行結果
店舗名 | 売上日 | 売上金額 |
---|---|---|
東京店 | 2025-07-02 | 15000 |
大阪店 | 2025-07-02 | 11000 |
JOINを使うメリット・デメリット
メリット
デメリット
JOINを使えば「集計」と「元データの結びつき」を直感的に書けるので、現場では非常によく使われています。
解決法3:ウィンドウ関数(ROW_NUMBER)を使う
MySQL 8.0以降では、ウィンドウ関数(分析関数)というとても強力な機能が使えるようになりました。
その中でも今回使うのは ROW_NUMBER()
という関数です。
この関数を使えば、各グループの中で順位をつけることができるんです。
ROW_NUMBER() で「最大値の行」に1位をつける
以下がその実装例です。
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY 店舗名
ORDER BY 売上金額 DESC
) AS rn
FROM 売上
) AS ranked
WHERE rn = 1;
最後に、WHERE rn = 1
で「各店舗の最大売上の行」だけを抽出しています。
実行結果
店舗名 | 売上日 | 売上金額 | rn |
---|---|---|---|
東京店 | 2025-07-02 | 15000 | 1 |
大阪店 | 2025-07-02 | 11000 | 1 |
rn = 1
の行だけを取り出しているので、まさに「最大の行だけ」を抜き出すことができます。
ウィンドウ関数のメリット・デメリット
メリット
デメリット
補足:最大値が複数あるときは?
ROW_NUMBER()
は「同じ最大値が複数あっても1行しか選ばれない」という特徴があります。
もし「最大値が複数ある場合は全部ほしい!」というときは、代わりに RANK()
を使いましょう。
SELECT *
FROM (
SELECT *, RANK() OVER (
PARTITION BY 店舗名
ORDER BY 売上金額 DESC
) AS rk
FROM 売上
) AS ranked
WHERE rk = 1;
このようにすれば、最大値が同点の行がすべて取得できます。
最近の現場では、「JOINかウィンドウ関数」というのが主流になりつつあります。
慣れると本当に書きやすく、メンテナンスもしやすいので、ぜひ今のうちに使いこなせるようになっておきましょう!
まとめ
「GROUP BY
と MAX()
を使えばグループごとの最大値が取れる」──これは間違いではありません。
でも、それだけでは**「最大値の行全体」までは取れない**という、意外な落とし穴があることも学びました。
本記事では、その壁を乗り越えるための3つの方法をご紹介しました。
✅ 1. サブクエリを使う方法
店舗ごとの最大値を別で計算し、それと一致する行を条件にして抽出。
→ 精度重視・MySQL全バージョン対応。
✅ 2. JOINを使う方法
最大値だけを抽出したサブクエリと、元テーブルを結合。
→ 書きやすく、実務でよく使われる。
✅ 3. ウィンドウ関数(ROW_NUMBER)を使う方法
MySQL 8.0以降で使える最強テクニック。
→ 可読性が高く、スマートに1位の行を抽出できる。
SQLのような「データ操作の基本」は、ちょっとした知識でグッと実務力が上がる分野です。
グループごとの最大値取得はその代表例。
一つの方法だけで満足せず、「どの状況でどの書き方が最適か?」を考えながら使い分けられると、SQLスキルは一段レベルアップします。
あなたの業務や学習の中で、今日紹介した方法が少しでも役に立てば幸いです!
コメント