web-dev-qa-db-ja.com

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

各グループセットの最大値を含む行をどのように取得しますか?

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

以下のような、個人、グループ、および年齢の列を含む表を考えて、各グループの中で最も年長の人をどのようにして取得しますか。 (グループ内の同点は最初のアルファベット順の結果を与えるべきです)

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  
188
Yarin

Mysqlでこれを行うための超簡単な方法があります。

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

Mysqlでは、notgroup-by-by列を許可することができます。その場合、mysqlはfirstを返すだけです。行。解決策は、まずグループごとに必要な行が最初になるようにデータを順序付けしてから、値を必要な列でグループ化することです。

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

注:これはmysql-onlyの解決策です。私が知っている他のすべてのデータベースでは、「集約されていない列はgroup by句にリストされていません」というメッセージでSQL構文エラーが発生します。このソリューションでは記載されていないの動作を使用しているので、remainsworkingと断言するテストを含めることをお勧めします。将来のバージョンのMySQLでこの動作が変更される予定です。

バージョン5.7の更新:

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

127
Bohemian

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

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のすべての行がGroup列の値が同じで、Age列の値が大きい行と一致します。列oにそのグループの最大値を持たないAgeからの行は、bからの1つ以上の行と一致します。

LEFT JOINは、NULLからのbsでいっぱいの行( 'グループ内で最大年齢なし')で、グループ内の最も古い人(そのグループ内で一人の人を含む)と一致させます。
INNER JOINを使用すると、これらの行は一致しなくなり、無視されます

WHERE文節は、NULLから抽出されたフィールド内にbを持つ行だけを保持します。彼らは各グループの最年長の人たちです。

さらなる読み

この解決策や他の多くのものは本で説明されています SQLアンチパターン:データベースプログラミングの落とし穴を避ける

247
axiac

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;
34

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;
27
Igor Kulagin

ランキング方式を使用しています。

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
3
sel

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;
2
user130268

axiacの解決策は、最終的には私にとって最も効果的でした。しかし、もう1つ複雑さがありました。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
Arthur C

私の解決策はあなたが1つの列だけを検索する必要がある場合にのみうまくいきます、しかし私のニーズのためにパフォーマンスの面で見いだされた最も良い解決策でした(それは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を使用して順序付けされた連結リストを作成し、それから最初のものだけにサブストリングします。

1

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
Marvin

Wordは予約されているので、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
0
Bae Cheol Shin

これが、mysqlで1グループあたりの最大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個の最大値を選択します

0
Vanko
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
Harshad

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

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
mayank kumar

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

出典: 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
Ray Foss

テーブル名を人にする

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
user3475425

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

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