174

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

次のような表のように、人、グループ、年齢の列がある場合、各グループの最年長 2 人をどのように取得しますか? (グループ内の同点はそれ以上の結果をもたらさないはずですが、最初の 2 つをアルファベット順に示します)

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

望ましい結果セット:

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

注:この質問は、グループ化されたSQL 結果の各グループの最大値を持つレコードを取得する - 各グループから単一のトップ行を取得するための前の質問に基づいており、@Bohemian から素晴らしい MySQL 固有の回答を受け取りました:

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

方法はわかりませんが、これを基に構築できれば幸いです。

4

12 に答える 12

103

これを行う1つの方法は、を使用したものですUNION ALLSQL 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;

デモを見る

于 2012-08-24T17:25:29.170 に答える
75

他のデータベースでは、 を使用してこれを行うことができます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


編集bluefeet が非常によく似た回答を投稿したことに気付きました: +1 to him. ただし、この回答には2つの小さな利点があります。

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

誰かを助けることができる場合に備えて、ここに残します。

于 2012-08-24T22:55:03.487 に答える
51

これを試して:

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

デモ

于 2012-08-24T17:40:10.303 に答える
32

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

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      

Bill Karwin からの各カテゴリの上位 10 レコードを選択するという回答に強く感銘を受けました。

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

もう 1 つ: 上記では、便宜上group、列を列に置き換えました。groupname

編集

同点の結果の欠落に関するOPのコメントをフォローアップし、すべての同点を表示するためにsnuffinの回答を増やしました. これは、以下に示すように、最後の行が同数の場合、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      
于 2012-08-24T17:37:59.533 に答える
19

大量の行がある場合、Snuffin ソリューションの実行は非常に遅いようです。Mark Byers/Rick James および Bluefeet ソリューションは、select の実行後に order by が適用されるため、私の環境 (MySQL 5.6) では機能しません。この問題を修正するための Marc Byers/Rick James のソリューション (複雑な選択を追加):

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秒未満で結果が返されました

于 2017-12-07T13:57:12.963 に答える
2

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
于 2016-12-05T14:25:07.323 に答える