1494

ドキュメント用にこの表があります(簡略版はこちら):

ID 回転 コンテンツ
1 1 ...
2 1 ...
1 2 ...
1 3 ...

ID ごとに 1 つの行を選択し、最大の収益のみを選択するにはどうすればよいですか?
上記のデータでは、結果には[1, 3, ...]との 2 つの行が含まれているはず[2, 1, ..]です。私はMySQLを使用しています。

現在、ループ内でチェックを使用してwhile、結果セットから古いリビジョンを検出して上書きしています。しかし、これが結果を達成するための唯一の方法ですか? SQLソリューションはありませんか?

4

27 に答える 27

2338

一目見ただけで...

必要なのは、集計関数GROUP BYを含む句だけです。MAX

SELECT id, MAX(rev)
FROM YourTable
GROUP BY id

それほど単純ではありませんよね?

content列も必要であることに気付きました。

これは SQL で非常によくある質問です。あるグループ識別子ごとに列に最大値がある行のデータ全体を見つけます。キャリアの中でよく耳にしました。実は、現職の技術面接で答えた質問の一つでした。

実際、スタック オーバーフロー コミュニティがそのような質問に対処するためだけに、「 」という 1 つのタグを作成したことは非常に一般的です。

基本的に、この問題を解決するには 2 つの方法があります。

単純なサブクエリで結合group-identifier, max-value-in-groupする

このアプローチでは、最初にサブクエリでgroup-identifier, max-value-in-group(上記で解決済み) を見つけます。group-identifier次に、テーブルを と の両方で等しいサブクエリに結合しますmax-value-in-group

SELECT a.id, a.rev, a.contents
FROM YourTable a
INNER JOIN (
    SELECT id, MAX(rev) rev
    FROM YourTable
    GROUP BY id
) b ON a.id = b.id AND a.rev = b.rev

自分自身との左結合、結合条件とフィルターの微調整

このアプローチでは、テーブルをそれ自体で結合したままにします。平等は に入るgroup-identifier。次に、2 つのスマートな動き:

  1. 2 番目の結合条件は、左側の値が右側の値よりも小さいことです
  2. ステップ 1 を実行すると、実際に最大値を持つ行がNULL右側に表示されます (LEFT JOIN覚えていますか?)。次に、結合された結果をフィルター処理して、右側が である行のみを表示しますNULL

したがって、次のようになります。

SELECT a.*
FROM YourTable a
LEFT OUTER JOIN YourTable b
    ON a.id = b.id AND a.rev < b.rev
WHERE b.id IS NULL;

結論

どちらのアプローチでも、まったく同じ結果が得られます。

max-value-in-groupforで2 つの行がある場合group-identifier、両方の行が両方のアプローチの結果になります。

どちらのアプローチも SQL ANSI と互換性があるため、その「フレーバー」に関係なく、お気に入りの RDBMS で動作します。

どちらのアプローチもパフォーマンスに適していますが、マイレージは異なる場合があります (RDBMS、DB 構造、インデックスなど)。したがって、一方のアプローチをもう一方のアプローチよりも優先する場合は、benchmark . そして、あなたにとって最も意味のあるものを選ぶようにしてください.

于 2011-10-12T19:43:53.237 に答える
350

私の好みは、できるだけ少ないコードを使用することです...

あなたはこれをIN 試してそれを行うことができます:

SELECT * 
FROM t1 WHERE (id,rev) IN 
( SELECT id, MAX(rev)
  FROM t1
  GROUP BY id
)

私の考えでは、それほど複雑ではありません...読みやすく、保守も簡単です。

于 2011-10-12T19:47:41.397 に答える
156

SQLウィンドウ関数ソリューションを提供する答えがなかったことに私は驚いています:

SELECT a.id, a.rev, a.contents
  FROM (SELECT id, rev, contents,
               ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
          FROM YourTable) a
 WHERE a.rank = 1 

SQL 標準 ANSI/ISO 標準 SQL:2003 に追加され、その後 ANSI/ISO 標準 SQL:2008 で拡張されたウィンドウ (またはウィンドウ処理) 関数は、現在すべての主要ベンダーで利用できます。同点の問題に対処するために使用できるランク関数の種類は他にもありますRANK, DENSE_RANK, PERSENT_RANK

于 2016-08-09T15:29:17.410 に答える
100

さらに別の解決策は、相関サブクエリを使用することです。

select yt.id, yt.rev, yt.contents
    from YourTable yt
    where rev = 
        (select max(rev) from YourTable st where yt.id=st.id)

(id,rev) にインデックスを付けると、サブクエリはほとんど単純なルックアップとしてレンダリングされます...

以下は、@AdrianCarneiro の回答 (サブクエリ、leftjoin) のソリューションとの比較です。これは、グループ サイズが 1 ~ 3 である ~100 万レコードの InnoDB テーブルを使用した MySQL 測定に基づいています。

完全なテーブル スキャンの場合、サブクエリ/leftjoin/相関のタイミングは 6/8/9 のように相互に関連していますが、直接ルックアップまたはバッチ ( id in (1,2,3)) に関しては、サブクエリは他のものよりもはるかに遅くなります (サブクエリを再実行するため)。ただし、左結合と相関ソリューションを速度で区別できませんでした。

leftjoin はグループ内に n*(n+1)/2 個の結合を作成するため、そのパフォーマンスはグループのサイズに大きく影響される可能性があります...

于 2014-01-23T14:16:11.100 に答える
46

パフォーマンスを保証することはできませんが、Microsoft Excel の制限に着想を得たトリックを次に示します。それはいくつかの良い機能を持っています

いい物

  • 同点の場合でも、1 つの「最大レコード」のみを強制的に返す必要があります (便利な場合もあります)。
  • 参加する必要はありません

アプローチ

これは少し見苦しく、rev列の有効な値の範囲についてある程度知っている必要があります。rev列が小数を含めて 0.00 から 999 までの数値であることがわかっていると仮定します。

要点は、必要なデータとともにプライマリ比較フィールドを文字列連結/パックすることにより、単一の合成列を作成することです。このようにして、SQL の MAX() 集計関数にすべてのデータを返すように強制できます (単一の列にパックされているため)。次に、データを解凍する必要があります。

上記の例を SQL で記述した場合、次のようになります。

SELECT id, 
       CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
       SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev 
FROM  (SELECT id, 
       CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
       FROM yourtable
      ) 
GROUP BY id

パッキングは、rev の値に関係なく、rev 列を強制的既知の文字長にすることから始まります。たとえば、

  • 3.2 は 1003.201 になります
  • 57 は 1057.001 になります
  • 923.88 は 1923.881 になります

正しく行えば、2 つの数値の文字列比較は、2 つの数値の数値比較と同じ "max" を生成し、部分文字列関数を使用して元の数値に簡単に戻すことができます (これは何らかの形で利用できます)。どこにでも)。

于 2013-06-30T06:02:30.600 に答える
43

一意の識別子? はい!ユニークな識別子!

MySQL DB を開発する最良の方法の 1 つは、それぞれを用意することid AUTOINCREMENTです (ソース MySQL.com)。これにより、ここでは説明しきれないほどのさまざまな利点が得られます。質問の問題は、その例に重複した ID があることです。これは、一意の識別子のこれらの大きな利点を無視すると同時に、すでにこれに精通している人々を混乱させます.

最もクリーンなソリューション

DBフィドル

MySQL の新しいバージョンはONLY_FULL_GROUP_BYデフォルトで有効になっており、ここにあるソリューションの多くはこの条件でのテストに失敗します。

DISTINCT それでも、 someuniquefieldMAX( whateverotherfieldtoselect )(*somethirdfieldなどを選択するだけ)で、結果やクエリの仕組みを理解する心配はありません。

SELECT DISTINCT t1.id, MAX(t1.rev), MAX(t2.content)
FROM Table1 AS t1
JOIN Table1 AS t2 ON t2.id = t1.id AND t2.rev = (
    SELECT MAX(rev) FROM Table1 t3 WHERE t3.id = t1.id
)
GROUP BY t1.id;
  • SELECT DISTINCT Table1.id, max(Table1.rev), max(Table2.content): DISTINCTsomefield、MAX()some otherfield を返します。最後MAX()は冗長です。これは 1 つの行に過ぎないことはわかっていますが、クエリで必要なためです。
  • FROM Employee: 検索対象のテーブル。
  • JOIN Table1 AS Table2 ON Table2.rev = Table1.rev: max(table1.rev) のコメントを取得する必要があるため、最初のテーブルに 2 番目のテーブルを結合します。
  • GROUP BY Table1.id: 各従業員の最上位に並べ替えられた Salary 行が結果として返されるように強制します。

OPの質問では「コンテンツ」が「...」だったため、これが機能することをテストする方法がないことに注意してください。それで、それを「..a」、「..b」に変更したので、実際に結果が正しいことがわかります。

id  max(Table1.rev) max(Table2.content)
1   3   ..d
2   1   ..b

なぜそれはきれいですか? DISTINCT()MAX()など、すべて MySQL インデックスをうまく利用しています。これはより速くなります。または、インデックスがあれば、すべての行を調べるクエリと比較すると、はるかに高速になります。

元のソリューション

無効にすると、ONLY_FULL_GROUP_BY引き続き use を使用できますがGROUP BY、ID ではなく Salary でのみ使用しています。

SELECT *
FROM
    (SELECT *
    FROM Employee
    ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
  • SELECT *: すべてのフィールドを返します。
  • FROM Employee: 検索対象のテーブル。
  • (SELECT *...)subquery : 給与でソートされたすべての人を返します。
  • GROUP BY employeesub.Salary: 各従業員の最上位に並べ替えられた Salary 行が結果として返されるように強制します。

一意の行のソリューション

リレーショナル データベースの定義に注意してください。「テーブルの各行には独自の一意のキーがあります。」これは、質問の例では、id一意である必要があることを意味します。その場合、次のようにすることができます。

SELECT *
FROM Employee
WHERE Employee.id = 12345
ORDER BY Employee.Salary DESC
LIMIT 1

願わくば、これが問題を解決し、DB で何が起こっているかを誰もがよりよく理解するのに役立つソリューションになることを願っています。

于 2016-09-14T00:28:36.640 に答える
23

このようなもの?

SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
    SELECT id, max(rev) as maxrev
    FROM yourtable
    GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
于 2011-10-12T19:48:45.670 に答える
14

NOT EXISTこの問題には、ベースのソリューションを使用するのが好きです。

SELECT 
  id, 
  rev
  -- you can select other columns here
FROM YourTable t
WHERE NOT EXISTS (
   SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)

これにより、グループ内の最大値を持つすべてのレコードが選択され、他の列を選択できるようになります。

于 2014-09-05T21:58:28.763 に答える
5

mySQLではありませんが、この質問を見つけて SQL を使用している他の人にとって、Cross Apply問題を解決する別の方法は、 MS SQLで使用することです

WITH DocIds AS (SELECT DISTINCT id FROM docs)

SELECT d2.id, d2.rev, d2.content
FROM DocIds d1
CROSS APPLY (
  SELECT Top 1 * FROM docs d
  WHERE d.id = d1.id
  ORDER BY rev DESC
) d2

SqlFiddle の例を次に示します

于 2014-05-30T13:47:53.067 に答える
5

これはこの問題に関して最もよくある質問であるため、別の回答をここにも再投稿します。

これを行う簡単な方法があるようです (ただし、MySQL のみ):

select *
from (select * from mytable order by id, rev desc ) x
group by id

この問題に対する簡潔でエレガントな回答を提供してくれた、この質問のユーザー Bohemian の回答を評価してください。

編集:このソリューションは多くの人に有効ですが、長期的には安定していない可能性があります。これは、MySQL が GROUP BY ステートメントが GROUP BY リストにない列に対して意味のある値を返すことを保証していないためです。したがって、このソリューションは自己責任で使用してください。

于 2014-07-03T14:33:34.277 に答える
4

私はこれを使用します:

select t.*
from test as t
join
   (select max(rev) as rev
    from test
    group by id) as o
on o.rev = t.rev

サブクエリの SELECT はあまり効率的ではないかもしれませんが、JOIN 句では使用できるようです。私はクエリの最適化の専門家ではありませんが、MySQL、PostgreSQL、FireBird で試してみましたが、非常にうまく機能します。

このスキーマは、複数の結合と WHERE 句で使用できます。それは私の作業例です(テーブル「しっかりした」に関するあなたの問題と同じ解決策):

select *
from platnosci as p
join firmy as f
on p.id_rel_firmy = f.id_rel
join (select max(id_obj) as id_obj
      from firmy
      group by id_rel) as o
on o.id_obj = f.id_obj and p.od > '2014-03-01'

10 代以上のレコードを持つテーブルで要求され、それほど強力ではないマシンで 0.01 秒もかかりません。

IN句は使用しません(上記のどこかで言及されているため)。IN は、サブクエリに基づいて構築されたクエリ フィルターではなく、定数の短いリストで使用するために指定されています。これは、スキャンされたレコードごとに IN のサブクエリが実行されるため、非常に長い時間がかかるクエリを作成できるためです。

于 2015-03-04T18:12:10.567 に答える
2

このソリューションは、YourTable から 1 つだけ選択するため、より高速です。sqlfiddle.com でのテストによると、MySQL と SQLite (SQLite の場合は DESC を削除) でのみ機能します。私がよく知らない他の言語で動作するように微調整できるかもしれません。

SELECT *
FROM ( SELECT *
       FROM ( SELECT 1 as id, 1 as rev, 'content1' as content
              UNION
              SELECT 2, 1, 'content2'
              UNION
              SELECT 1, 2, 'content3'
              UNION
              SELECT 1, 3, 'content4'
            ) as YourTable
       ORDER BY id, rev DESC
   ) as YourTable
GROUP BY id
于 2014-01-29T07:49:11.443 に答える
2

ここにそれを行う良い方法があります

次のコードを使用します。

with temp as  ( 
select count(field1) as summ , field1
from table_name
group by field1 )
select * from temp where summ = (select max(summ) from temp)
于 2015-01-07T11:36:08.023 に答える
2

ここに別の解決策がありますそれが誰かを助けることを願っています

Select a.id , a.rev, a.content from Table1 a
inner join 
(SELECT id, max(rev) rev FROM Table1 GROUP BY id) x on x.id =a.id and x.rev =a.rev
于 2017-06-20T10:10:35.127 に答える
1

説明

これは純粋な SQL ではありません。これは SQLAlchemy ORM を使用します。

SQLAlchemy のヘルプを探してここに来たので、Adrian Carneiro の回答を python/SQLAlchemy バージョン、特に外部結合部分で複製します。

このクエリは、次の質問に答えます。

「このグループのレコード (同じ ID に基づく) のうち、最も高いバージョン番号を持つレコードを返してもらえますか」.

これにより、レコードを複製し、更新し、バージョン番号を増やし、時間の経過に伴う変化を示すことができるように古いバージョンのコピーを保持できます。

コード

MyTableAlias = aliased(MyTable)
newest_records = appdb.session.query(MyTable).select_from(join(
    MyTable, 
    MyTableAlias, 
    onclause=and_(
        MyTable.id == MyTableAlias.id,
        MyTable.version_int < MyTableAlias.version_int
    ),
    isouter=True
    )
).filter(
    MyTableAlias.id  == None,
).all()

PostgreSQL データベースでテスト済み。

于 2019-02-22T15:18:26.237 に答える