グループ化されたSQL結果の各グループの最大値を持つレコードを取得する


229

グループ化された各セットの最大値を含む行を取得するにはどうすればよいですか?

私はこの質問について過度に複雑なバリエーションを見てきましたが、どれも良い答えはありません。私は可能な限り簡単な例をまとめようとしました:

以下のようなテーブルにperson、group、ageの列がある場合、どのようにして各グループの最も古い人を取得できますか?(グループ内のネクタイは最初のアルファベット順の結果を与える必要があります)

Person | Group | Age
---
Bob  | 1     | 32  
Jill | 1     | 34  
Shawn| 1     | 42  
Jake | 2     | 29  
Paul | 2     | 36  
Laura| 2     | 39  

望ましい結果セット:

Shawn | 1     | 42    
Laura | 2     | 39  

3
注意:承認された回答は、2012年に書かれたときに機能しました。ただし、コメントに記載されているように、複数の理由で機能しなくなりました。
リックジェームズ

回答:


132

mysqlでこれを行う非常に簡単な方法があります。

select * 
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`

mysql ではgroup-by以外の列を集約できないため、これが機能します。この場合、mysqlは最初の行を返すだけです。解決策は、まずグループごとに必要な行が最初になるようにデータを並べ替え、次に値を求める列でグループ化することです。

max()etc を検索しようとする複雑なサブクエリを回避し、同じ最大値を持つ行が複数ある場合に複数の行を返すという問題を回避します(他の回答と同様)。

注:これはmysqlのみのソリューションです。私が知っている他のすべてのデータベースは、「非集計列がgroup by句にリストされていません」などのメッセージを含むSQL構文エラーをスローします。このソリューションは、使用しているため、文書化されていない行動を、より慎重にそれがいることを主張するためのテストを含めることが残っているのMySQLの将来のバージョンは、この動作を変更する必要があります取り組んでいます。

バージョン5.7アップデート:

バージョン5.7以降、このsql-mode設定にはONLY_FULL_GROUP_BYデフォルトで含まれているため、この設定を機能させるには、このオプションを使用しないでください(サーバーのオプションファイルを編集して、この設定を削除します)。


66
「mysqlは最初の行を返すだけです。」-多分これはそれがどのように機能するかですが、保証されていません。ドキュメントは言う:「サーバは、彼らが同じでない限り、選択された値は不定です、各グループからの任意の値を自由に選択することができます。」。サーバーは行を選択しませんが、SELECT句に表示され、集計関数を使用して計算されない各列または式の値(必ずしも同じ行からのものではない)を選択します。
axiac、2015年

16
この動作はMySQL 5.7.5で変更され、SELECT句の列は機能的にGROUP BY列に依存していないため、デフォルトではこのクエリが拒否されます。それを受け入れるように構成されている場合( `ONLY_FULL_GROUP_BY`が無効になっている場合)、以前のバージョンと同様に機能します(つまり、これらの列の値は不確定です)。
axiac、2015年

17
この回答に多くの賛成票が集まったことに驚いています。それは間違っており、悪いです。このクエリの動作は保証されていません。サブクエリ内のデータは、order by句に関係なく、順序付けされていないセットです。MySQL 実際にレコードを今すぐ順序付けして保持する可能性があります、将来のバージョンで停止した場合でも、規則に違反することはありません。次に、GROUP BY1つのレコードに圧縮しますが、すべてのフィールドはレコードから任意に選択されます。それはあり、MySQLは現在、単に常に最初の行を選び、それは全く同じように他の任意の行または偶数値を選ぶことができるとも異なる、将来のバージョンの行。
Thorsten Kettner、2016年

9
さて、ここでは同意しません。私は、たまたま現在機能しているだけの文書化されていない機能を使用せず、うまくいけばこれをカバーするいくつかのテストに依存しています。現在の実装では完全な最初のレコードが得られ、代わりに不確定な値が返される可能性があるとドキュメントで明確に述べられているが、それを使用していることは幸運であることがわかっています。一部の単純なセッションまたはデータベース設定は、これをいつでも変更する可能性があります。これは危険すぎると思います。
Thorsten Kettner、2016年

3
この答えは間違っているようです。ドキュメントごとに、サーバーは各グループから任意の値を自由に選択できます。さらに、各グループからの値の選択は、ORDER BY句を追加しても影響を受けません。結果セットのソートは値が選択された後に行われ、ORDER BYはサーバーが選択する各グループ内の値に影響を与えません。
Tgr

296

正しい解決策は次のとおりです。

SELECT o.*
FROM `Persons` o                    # 'o' from 'oldest person in group'
  LEFT JOIN `Persons` b             # 'b' from 'bigger age'
      ON o.Group = b.Group AND o.Age < b.Age
WHERE b.Age is NULL                 # bigger age not found

使い方:

これoは、の各行bを、columnの値が同じでcolumnのGroup値が大きいすべての行と一致しますAge。列にoグループの最大値がない行は、のAge1つ以上の行と一致しますb

LEFT JOINそれはの完全な行を持つ(そのグループに一人でいる者を含む)のグループで最古の人物と一致しますNULLから、S b(「グループでノー最大の年齢を」)。
を使用INNER JOINすると、これらの行が一致しなくなり、無視されます。

WHERE句が持つ行のみ続けるNULLから抽出されたフィールドで秒b。彼らは各グループの最年長者です。

さらに読む

このソリューションと他の多くの本は、SQLアンチパターンの本で説明されています:データベースプログラミングの落とし穴を回避する


43
ところで、これはo.Age = b.Age、たとえばグループ2のPaulがLauraのように39にいる場合、同じグループの2つ以上の行を返す可能性があります。しかし、我々は我々が行うことができ、そのような行動をしたくない場合は:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
トドル

8
信じられない!20Mレコードの場合、それはより速く、「ナイーブ」アルゴリズムよりも50倍のようなものだ(MAX()でサブクエリに対して参加)
user2706534

3
@Todorコメントと完全に連携します。さらにクエリ条件がある場合は、FROMおよびLEFT JOINに追加する必要があることを付け加えます。LIKE (SELECT * FROM Person WHERE Age!= 32)o LEFT JOIN(SELECT * FROM Person WHERE Age!= 32)b -32
歳のユーザー

1
@AlainZelinkは、サブクエリを導入しないようにするために、これらの「さらなるクエリ条件」を最終的なWHERE条件リストに入れた方がよいのではないですか。これは、元の@ axiacの回答では不要でした。
tarilabs 2015

5
このソリューションは機能しました。ただし、同じIDを共有する10,000以上の行を使用しようとすると、スロークエリログで報告され始めました。インデックス付きの列で結合していました。まれなケースですが、言及する価値があると考えました。
chaseisabelle

50

MAX(Group)およびをプルするサブクエリに対して結合できAgeます。この方法は、ほとんどのRDBMSで移植可能です。

SELECT t1.*
FROM yourTable t1
INNER JOIN
(
    SELECT `Group`, MAX(Age) AS max_age
    FROM yourTable
    GROUP BY `Group`
) t2
    ON t1.`Group` = t2.`Group` AND t1.Age = t2.max_age;

マイケル、ありがとう-しかし、ボヘミアンのコメントによると、ネクタイに複数の行を返す問題に対する答えはありますか?
Yarin、2012

1
@Yarinたとえばwhereのように2つの行がある場合Group = 2, Age = 20、サブクエリはそれらの1つを返しますが、join ON句はそれらの両方に一致するため、他の列のvalが異なるにもかかわらず、同じグループ/年齢で2行が返されます。 1つではなく。
Michael Berkowski、2012

ボヘミアンのMySQLのみのルートを使用しない限り、結果をグループごとに1つに制限することは不可能だと言っているのでしょうか。
Yarin、2012

@Yarinは不可能ではありません。追加の列がある場合はさらに作業が必要です-おそらく別のネストされたサブクエリが、グループ/年齢の各ペアの最大関連IDをプルし、それに対して結合して、IDに基づいて行の残りを取得します。
Michael Berkowski、2012

これは受け入れられた回答でなければなりません(現在受け入れられている回答は、他のほとんどのRDBMSでは失敗し、実際には多くのバージョンのMySQLでも失敗します)。
Tim Biegeleisen、2018

28

SQLite(おそらくMySQL)の簡単な解決策:

SELECT *, MAX(age) FROM mytable GROUP BY `Group`;

ただし、PostgreSQLおよびおそらく他の一部のプラットフォームでは機能しません。

PostgreSQLでは、DISTINCT ON句を使用できます

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;

@ボヘミアン申し訳ありませんが、非集計列が含まれているため、これはMySQLのみです
Cec

2
@IgorKulagin-Postgresでは機能しない-エラーメッセージ:列 "mytable.id"はGROUP BY句に表示されるか、集計関数で使用される必要があります
Yarin

13
MySQLクエリは、多くの場合偶然にのみ機能する可能性があります。「SELECT *」は、所属するMAX(age)に対応しない情報を返す場合があります。この答えは間違っています。これはおそらくSQLiteにも当てはまります。
Albert Hendriks、2016年

2
しかし、これはグループ化された列と最大列を選択する必要がある場合に適しています。これは上記の要件(「ボブ」、1、42)には適合しませんが、期待される結果は(「ショーン」、
1、42

1
postgresに
最適

4

ランキング方式を使用。

SELECT @rn :=  CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END AS rn,  
   @prev_grp :=groupa,
   person,age,groupa  
FROM   users,(SELECT @rn := 0) r        
HAVING rn=1
ORDER  BY groupa,age DESC,person

sel-説明が必要です-私は今まで見たこともありません-それは:=何ですか?
Yarin、2012


私はこれについて掘り下げる必要があります
Yarin

3

MySQLにrow_number関数があるかどうかは不明です。その場合は、それを使用して目的の結果を得ることができます。SQL Serverでは、次のようなことができます。

CREATE TABLE p
(
 person NVARCHAR(10),
 gp INT,
 age INT
);
GO
INSERT  INTO p
VALUES  ('Bob', 1, 32);
INSERT  INTO p
VALUES  ('Jill', 1, 34);
INSERT  INTO p
VALUES  ('Shawn', 1, 42);
INSERT  INTO p
VALUES  ('Jake', 2, 29);
INSERT  INTO p
VALUES  ('Paul', 2, 36);
INSERT  INTO p
VALUES  ('Laura', 2, 39);
GO

SELECT  t.person, t.gp, t.age
FROM    (
         SELECT *,
                ROW_NUMBER() OVER (PARTITION BY gp ORDER BY age DESC) row
         FROM   p
        ) t
WHERE   t.row = 1;

1
8.0以降、サポートされています。
IljaEverilä18年

2

axiacのソリューションは、最終的に私にとって最も効果的でした。ただし、2つの列から計算された「最大値」の計算がさらに複雑になりました。

同じ例を使用してみましょう:各グループで最も古い人が欲しいです。同じ年齢の人がいる場合は、一番背の高い人を選びます。

この動作を得るには、左結合を2回実行する必要がありました。

SELECT o1.* WHERE
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o1
LEFT JOIN
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o2
ON o1.Group = o2.Group AND o1.Height < o2.Height 
WHERE o2.Height is NULL;

お役に立てれば!これを行うにはもっと良い方法があるはずです...


2

私の解決策は、1つの列のみを取得する必要がある場合にのみ機能しますが、私のニーズには、パフォーマンスの観点から見た最良の解決策がありました(単一のクエリのみを使用します!)。

SELECT SUBSTRING_INDEX(GROUP_CONCAT(column_x ORDER BY column_y),',',1) AS xyz,
   column_z
FROM table_name
GROUP BY column_z;

GROUP_CONCATを使用して、順序付けられた連結リストを作成し、最初のリストのみに部分文字列します。


group_concat内の同じキーでソートすることで複数の列を取得できることを確認できますが、列ごとに個別のgroup_concat / index / substringを書き込む必要があります。
Rasika

ここでの利点は、group_concat内のソートに複数の列を追加できることです。これにより、結合を簡単に解決し、グループごとに1つのレコードのみを保証できます。シンプルで効率的なソリューションでよくできました!
Rasika

2

私は使用することで簡単な解決策を持っています WHERE IN

SELECT a.* FROM `mytable` AS a    
WHERE a.age IN( SELECT MAX(b.age) AS age FROM `mytable` AS b GROUP BY b.group )    
ORDER BY a.group ASC, a.person ASC

1

CTEの使用-共通テーブル式:

WITH MyCTE(MaxPKID, SomeColumn1)
AS(
SELECT MAX(a.MyTablePKID) AS MaxPKID, a.SomeColumn1
FROM MyTable1 a
GROUP BY a.SomeColumn1
  )
SELECT b.MyTablePKID, b.SomeColumn1, b.SomeColumn2 MAX(b.NumEstado)
FROM MyTable1 b
INNER JOIN MyCTE c ON c.MaxPKID = b.MyTablePKID
GROUP BY b.MyTablePKID, b.SomeColumn1, b.SomeColumn2

--Note: MyTablePKID is the PrimaryKey of MyTable

1

以下のOracleでは、クエリで目的の結果を得ることができます。

SELECT group,person,Age,
  ROWNUMBER() OVER (PARTITION BY group ORDER BY age desc ,person asc) as rankForEachGroup
  FROM tablename where rankForEachGroup=1

0
with CTE as 
(select Person, 
[Group], Age, RN= Row_Number() 
over(partition by [Group] 
order by Age desc) 
from yourtable)`


`select Person, Age from CTE where RN = 1`

0

あなたも試すことができます

SELECT * FROM mytable WHERE age IN (SELECT MAX(age) FROM mytable GROUP BY `Group`) ;

1
ありがとうございます。これは、ネクタイがある年齢の複数のレコードを返します
Yarin

また、グループ1に39歳の場合、このクエリは正しくありません。その場合、グループ1の最大年齢が高くても、その人物も選択されます。
ジョシュアリチャードソン

0

Groupは予約語であるため、列名として使用しません。ただし、次のSQLは機能します。

SELECT a.Person, a.Group, a.Age FROM [TABLE_NAME] a
INNER JOIN 
(
  SELECT `Group`, MAX(Age) AS oldest FROM [TABLE_NAME] 
  GROUP BY `Group`
) b ON a.Group = b.Group AND a.Age = b.oldest

ありがとうございます。これは、ネクタイがある年齢の複数のレコードを返します
Yarin

@ヤーリンはどのようにして正しい最も古い人を決定しますか?複数の回答が最も正解であると思われます。それ以外の場合は、制限と順序を使用します
Duncan

0

この方法には、別の列でランク付けでき、他のデータを破棄しないという利点があります。最も重いものを最初にリストして、アイテムの列を使用して注文をリストしようとしている状況で非常に役立ちます。

ソース:http : //dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

SELECT person, group,
    GROUP_CONCAT(
        DISTINCT age
        ORDER BY age DESC SEPARATOR ', follow up: '
    )
FROM sql_table
GROUP BY group;

0

テーブル名を人にしましょう

select O.*              -- > O for oldest table
from people O , people T
where O.grp = T.grp and 
O.Age = 
(select max(T.age) from people T where O.grp = T.grp
  group by T.grp)
group by O.grp; 

0

ID(およびすべての列)がmytableから必要な場合

SELECT
    *
FROM
    mytable
WHERE
    id NOT IN (
        SELECT
            A.id
        FROM
            mytable AS A
        JOIN mytable AS B ON A. GROUP = B. GROUP
        AND A.age < B.age
    )

0

これは私がmysqlでグループあたり最大N行を取得する方法です

SELECT co.id, co.person, co.country
FROM person co
WHERE (
SELECT COUNT(*)
FROM person ci
WHERE  co.country = ci.country AND co.id < ci.id
) < 1
;

使い方:

  • テーブルへの自己結合
  • グループは co.country = ci.country
  • グループあたりのN個の要素は) < 1、3 個の要素の場合はによって制御されます-)<3
  • 最大値または最小値を取得する方法は次の条件によって異なります。 co.id < ci.id
    • co.id <ci.id-最大
    • co.id> ci.id-最小

ここに完全な例:

mysqlはグループごとにn個の最大値を選択します

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