web-dev-qa-db-ja.com

グループ化された結果の各グループの上位nレコードを取得します

以下は可能な限り最も単純な例ですが、どのソリューションでも、上位n個の結果が必要な場合にスケーリングできる必要があります。

以下のようなテーブルに人、グループ、年齢の列がある場合、各グループの最も古い2人をどのように取得しますか?(グループ内の関係これ以上の結果は得られませんが、最初の2つをアルファベット順に指定してください)

 + -------- + ------- + ----- + 
 |人|グループ|年齢| 
 + -------- + ------- + ----- + 
 |ボブ| 1 | 32 | 
 |ジル| 1 | 34 | 
 |ショーン| 1 | 42 | 
 |ジェイク| 2 | 29 | 
 |ポール| 2 | 36 | 
 |ローラ| 2 | 39 | 
 + -------- + ------- + ----- + 

望ましい結果セット:

 + -------- + ------- + ----- + 
 |ショーン| 1 | 42 | 
 |ジル| 1 | 34 | 
 |ローラ| 2 | 39 | 
 |ポール| 2 | 36 | 
 + -------- + ------- + ----- + 

注:この質問は以前のものに基づいています- グループ化されたSQL結果の各グループの最大値でレコードを取得 -取得のため各グループの1行で、@ BohemianからMySQL固有の優れた回答を受け取りました。

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

方法はわかりませんが、これを構築できることを楽しみにしています。

128
Yarin

これを行う1つの方法は、UNION ALLを使用することです( SQL Fiddle with Demo を参照)。これは2つのグループで機能します。3つ以上のグループがある場合は、group番号を指定し、各groupにクエリを追加する必要があります。

(
  select *
  from mytable 
  where `group` = 1
  order by age desc
  LIMIT 2
)
UNION ALL
(
  select *
  from mytable 
  where `group` = 2
  order by age desc
  LIMIT 2
)

これを行うにはさまざまな方法があります。この記事を参照して、状況に最適なルートを決定してください。

http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/

編集:

これもあなたのために働くかもしれません、それは各レコードの行番号を生成します。上記のリンクの例を使用すると、行番号が2以下のレコードのみが返されます。

select person, `group`, age
from 
(
   select person, `group`, age,
      (@num:=if(@group = `group`, @num +1, if(@group := `group`, 1, 1))) row_number 
  from test t
  CROSS JOIN (select @num:=0, @group:=null) c
  order by `Group`, Age desc, person
) as x 
where x.row_number <= 2;

デモ を参照してください

82
Taryn

他のデータベースでは、ROW_NUMBERを使用してこれを行うことができます。 MySQLはROW_NUMBERをサポートしませんが、変数を使用してエミュレートできます:

SELECT
    person,
    groupname,
    age
FROM
(
    SELECT
        person,
        groupname,
        age,
        @rn := IF(@prev = groupname, @rn + 1, 1) AS rn,
        @prev := groupname
    FROM mytable
    JOIN (SELECT @prev := NULL, @rn := 0) AS vars
    ORDER BY groupname, age DESC, person
) AS T1
WHERE rn <= 2

オンラインで動作することを確認してください: sqlfiddle


Editbluefeetが非常によく似た答えを投稿していることに気付いた:彼に+1。ただし、この回答には2つの小さな利点があります。

  1. 単一のクエリです。変数はSELECTステートメント内で初期化されます。
  2. 質問(名前のアルファベット順)で説明されているように、関係を処理します。

だから、誰かを助けることができるなら、ここに置いておきます.

56
Mark Byers

これを試して:

SELECT a.person, a.group, a.age FROM person AS a WHERE 
(SELECT COUNT(*) FROM person AS b 
WHERE b.group = a.group AND b.age >= a.age) <= 2 
ORDER BY a.group ASC, a.age DESC

DEMO

34
snuffn

自己結合の使用はどうですか:

CREATE TABLE mytable (person, groupname, age);
INSERT INTO mytable VALUES('Bob',1,32);
INSERT INTO mytable VALUES('Jill',1,34);
INSERT INTO mytable VALUES('Shawn',1,42);
INSERT INTO mytable VALUES('Jake',2,29);
INSERT INTO mytable VALUES('Paul',2,36);
INSERT INTO mytable VALUES('Laura',2,39);

SELECT a.* FROM mytable AS a
  LEFT JOIN mytable AS a2 
    ON a.groupname = a2.groupname AND a.age <= a2.age
GROUP BY a.person
HAVING COUNT(*) <= 2
ORDER BY a.groupname, a.age DESC;

私に与えます:

a.person    a.groupname  a.age     
----------  -----------  ----------
Shawn       1            42        
Jill        1            34        
Laura       2            39        
Paul        2            36      

各カテゴリのトップ10レコードを選択 に対するBill Karwinの回答に強く触発されました

また、私はSQLiteを使用していますが、これはMySQLで動作するはずです。

別のこと:上記では、便宜上、group列をgroupname列に置き換えました。

編集

タイの結果が欠落していることに関するOPのコメントに続いて、スナフィンの回答を増やして、すべてのタイを表示しました。つまり、最後の行が同数の場合、以下に示すように、3行以上を返すことができます。

.headers on
.mode column

CREATE TABLE foo (person, groupname, age);
INSERT INTO foo VALUES('Paul',2,36);
INSERT INTO foo VALUES('Laura',2,39);
INSERT INTO foo VALUES('Joe',2,36);
INSERT INTO foo VALUES('Bob',1,32);
INSERT INTO foo VALUES('Jill',1,34);
INSERT INTO foo VALUES('Shawn',1,42);
INSERT INTO foo VALUES('Jake',2,29);
INSERT INTO foo VALUES('James',2,15);
INSERT INTO foo VALUES('Fred',1,12);
INSERT INTO foo VALUES('Chuck',3,112);


SELECT a.person, a.groupname, a.age 
FROM foo AS a 
WHERE a.age >= (SELECT MIN(b.age)
                FROM foo AS b 
                WHERE (SELECT COUNT(*)
                       FROM foo AS c
                       WHERE c.groupname = b.groupname AND c.age >= b.age) <= 2
                GROUP BY b.groupname)
ORDER BY a.groupname ASC, a.age DESC;

私に与えます:

person      groupname   age       
----------  ----------  ----------
Shawn       1           42        
Jill        1           34        
Laura       2           39        
Paul        2           36        
Joe         2           36        
Chuck       3           112      
31
user610650

これをチェックしてください:

SELECT
  p.Person,
  p.`Group`,
  p.Age
FROM
  people p
  INNER JOIN
  (
    SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`
    UNION
    SELECT MAX(p3.Age) AS Age, p3.`Group` FROM people p3 INNER JOIN (SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`) p4 ON p3.Age < p4.Age AND p3.`Group` = p4.`Group` GROUP BY `Group`
  ) p2 ON p.Age = p2.Age AND p.`Group` = p2.`Group`
ORDER BY
  `Group`,
  Age DESC,
  Person;

SQLフィドル: http://sqlfiddle.com/#!2/cdbb6/15

10
Travesty3

Snuffinソリューションは、行がたくさんあり、Mark Byers/Rick JamesおよびBluefeetソリューションが私の環境(MySQL 5.6)で動作しない場合、実行が非常に遅いようです。selectbyの実行後にorder byが適用されるため、ここにバリアントがありますこの問題を解決するためのマーク・バイヤーズ/リック・ジェームスのソリューション(追加のインブリケートされた選択):

select person, groupname, age
from
(
    select person, groupname, age,
    (@rn:=if(@prev = groupname, @rn +1, 1)) as rownumb,
    @prev:= groupname 
    from 
    (
        select person, groupname, age
        from persons 
        order by groupname ,  age desc, person
    )   as sortedlist
    JOIN (select @prev:=NULL, @rn :=0) as vars
) as groupedlist 
where rownumb<=2
order by groupname ,  age desc, person;

500万行のテーブルで同様のクエリを実行したところ、3秒未満で結果が返されました

8
Laurent PELE

他の回答が十分に速くない場合 このコード を試してください:

SELECT
        province, n, city, population
    FROM
      ( SELECT  @prev := '', @n := 0 ) init
    JOIN
      ( SELECT  @n := if(province != @prev, 1, @n + 1) AS n,
                @prev := province,
                province, city, population
            FROM  Canada
            ORDER BY
                province   ASC,
                population DESC
      ) x
    WHERE  n <= 3
    ORDER BY  province, n;

出力:

+---------------------------+------+------------------+------------+
| province                  | n    | city             | population |
+---------------------------+------+------------------+------------+
| Alberta                   |    1 | Calgary          |     968475 |
| Alberta                   |    2 | Edmonton         |     822319 |
| Alberta                   |    3 | Red Deer         |      73595 |
| British Columbia          |    1 | Vancouver        |    1837970 |
| British Columbia          |    2 | Victoria         |     289625 |
| British Columbia          |    3 | Abbotsford       |     151685 |
| Manitoba                  |    1 | ...
6
Rick James

これを共有したかったのは、作業中のJavaプログラムでこれを簡単に実装する方法を探すのに長い時間を費やしたからです。これは、探している出力を提供するものではなく、近いものです。 GROUP_CONCAT()というmysqlの関数は、各グループで返す結果の数を指定するのに非常にうまく機能しました。 LIMITまたはCOUNTを使用してこれを実行しようとする他の凝った方法を使用することは、私にとってはうまくいきませんでした。そのため、変更された出力を受け入れたい場合、それは素晴らしい解決策です。学生ID、性別、GPAを含む「student」というテーブルがあるとします。性別ごとに5 gpaを超えたいとしましょう。次に、このようなクエリを書くことができます

SELECT sex, SUBSTRING_INDEX(GROUP_CONCAT(cast(gpa AS char ) ORDER BY gpa desc), ',',5) 
AS subcategories FROM student GROUP BY sex;

パラメータ「5」は、各行に連結するエントリの数を示します。

そして、出力は次のようになります

+--------+----------------+
| Male   | 4,4,4,4,3.9    |
| Female | 4,4,3.9,3.9,3.8|
+--------+----------------+

ORDER BY変数を変更して、別の方法で並べ替えることもできます。したがって、学生の年齢があった場合、「gpa desc」を「age desc」に置き換えることができます。 group byステートメントに変数を追加して、出力の列を増やすこともできます。したがって、これは非常に柔軟で、結果を一覧表示するだけで問題ない場合に適切に機能することがわかりました。

2
Jon Bown

SQL Serverのrow_numer()は、以下のように簡単に結果を取得できる強力な関数です

select Person,[group],age
from
(
select * ,row_number() over(partition by [group] order by age desc) rn
from mytable
) t
where rn <= 2
0
Prakash