web-dev-qa-db-ja.com

SQLは、列の最大値を持つ行のみを選択します

私は文書用のこの表を持っています(簡略版はこちら):

+------+-------+--------------------------------------+
| id   | rev   | content                              |
+------+-------+--------------------------------------+
| 1    | 1     | ...                                  |
| 2    | 1     | ...                                  |
| 1    | 2     | ...                                  |
| 1    | 3     | ...                                  |
+------+-------+--------------------------------------+

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

現在私はwhileループのチェックを使って、結果セットからの古いrevを検出して上書きしています。しかし、これが結果を達成するための唯一の方法ですか? _ sql _ 解決策はありませんか?

更新
答えが示すように、isSQLの解決策、そして ここではsqlfiddleのデモ があります。 

更新2
上記のsqlfiddle、質問の支持率が回答の支持率を上回っています。これは意図していません。受け入れられた答え.

994

一目見ただけで...

必要なのは、MAX集約関数を持つGROUP BY節だけです。

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

それほど単純なことではありませんか。

私はあなたがcontentカラムも必要であることに気づきました。

これはSQLでよくある質問です。グループ識別子ごとに、列に最大値を持つ行のデータ全体を検索します。私は私のキャリアの間にそれをたくさん聞いた。実は、それは私の現在の仕事の技術面接で答えた質問の1つでした。 

StackOverflowコミュニティがそのような質問に対処するために単一のタグを作成したのは、実際には非常に一般的です: グループごとの最大数

基本的に、あなたはその問題を解決するための2つのアプローチがあります:

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

このアプローチでは、最初にサブクエリの中にgroup-identifier, max-value-in-group(すでに上で解決済み)を見つけます。次に、group-identifiermax-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-groupgroup-identifierを含む2つの行がある場合、両方の方法で両方の行が結果に含まれます。

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

どちらの方法もパフォーマンスに優しいですが、走行距離は異なる場合があります(RDBMS、DB構造、インデックスなど)。ですから、他の方法よりも1つの方法を選択すると、 benchmark となります。そして、あなたにとって最も意味のあるものを選ぶようにしてください。

1579
Adrian Carneiro

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

IN を使ってそれを行うことができます。

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

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

204
Kevin Burton

さらに別の解決策は、相関副照会を使用することです。

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の回答(subquery、leftjoin)の解決策と、InnoDBの100万レコードのテーブルを使用したグループサイズの比較です。1-3。

全表スキャンの場合、副問合せ/左結合/相関タイミングは6/8/9のように相互に関連しますが、直接問合せまたはバッチ(id in (1,2,3))になると、副問合せは他の副問合せよりはるかに遅くなります(副問合せの再実行による)。しかし私は左結合と相関解をスピードで区別することができませんでした。

最後の注意点として、leftjoinはグループ内でn *(n + 1)/ 2個のジョインを作成するため、そのパフォーマンスはグループのサイズによって大きく左右されます。

66
Vajk Hermecz

私は答えが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

54
topchef

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

良いもの

  • 同点があっても強制的に1つの「最大レコード」のみを返すようにします(場合によっては便利です)。
  • 参加は不要です

_アプローチ_

これは少し醜いので、 rev 列の有効な値の範囲について何か知っておく必要があります。 rev 列は小数点を含めて0.00から999の間の数であることを知っているとしましょう。しかし小数点の右側には2桁しかないことになります(例えば34.17は有効値).

要点は、あなたが欲しいデータと共に一次比較フィールドを文字列連結/パックすることによって単一の合成カラムを作成することです。このようにして、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"になるはずで、substring関数を使用して元の数値に戻すのは簡単です。どこにでも)。

44
David Foster

私はこれが最も簡単な解決策だと思います:

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

たまたま1行だけが必要な場合は、さらに簡単です。

SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1

また、他の目的に分解し、理解し、変更するのが最も簡単だと思います。

  • Employee.Salary DESCによる注文:最高の給与を最初にして、給与で結果を並べ替えます。
  • 制限1:結果を1つだけ返します。

このアプローチを理解すると、これらの同様の問題の解決は簡単になります。最低給与の従業員の獲得(DESCをASCへ変更)、トップ10の従業員の獲得(LIMIT 1からLIMIT 10へ変更) Employee.Sorry by Employee.Commission)など.

25
HoldOffHunger

このようなもの?

SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
    SELECT id, max(rev) as maxrev FROM yourtable
    WHERE yourtable
    GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
18
Marc B

これはこの問題に関して最も一般的な質問なので、ここでも同様にもう1つの回答を投稿します。

これを行うためのより簡単な方法があるように見えます(ただし MySQLの場合のみ )。

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

ユーザーBohemianの回答を信用してください in この質問 この問題に対する簡潔でエレガントな回答を提供するための/.

編集:この解決法は多くの人に有効ですが、MySQLはGROUP BYステートメントがGROUP BYリストにない列に意味のある値を返すことを保証しないので、長期的には安定しないかもしれません。だからあなた自身の責任でこの解決策を使用してください

6
Yura

この問題にはNOT EXISTベースの解決策を使用するのが好きです。

SELECT id, rev
FROM YourTable t
WHERE NOT EXISTS (
   SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)
6
Bulat

私がこれまで言及したことがほとんどない3つ目の解決策はMySQL固有であり、このように見えます:

SELECT id, MAX(rev) AS rev
 , 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content
FROM t1
GROUP BY id

はい、それはひどいように見えます(文字列への変換、逆変換など)が、私の経験では通常他の解決策より速いです。たぶんそれは私のユースケースのためのものですが、私は何百万ものレコードと多くのユニークなIDを持つテーブルでそれを使いました。たぶんそれはMySQLが他の解決策を最適化するのがかなり悪いからです(少なくとも私がこの解決策を考え出した5.0日以内に)。

重要なことの1つは、GROUP_CONCATが作成できる文字列の長さが最大であることです。おそらくgroup_concat_max_len変数を設定することによってこの制限を引き上げたいでしょう。また、多数の行がある場合、これがスケーリングの制限になることに注意してください。

とにかく、あなたのコンテンツフィールドが既にテキストであるならば、上記は直接働きません。その場合は、おそらく\ 0のように別の区切り文字を使いたいでしょう。また、group_concat_max_lenの上限に早く達することもあります。

5
Jannes

私は思う、あなたはこれが欲しいですか?

select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)  

SQL Fiddle: ここで確認してください

4
Abhishek Rana

Selectステートメントに多数のフィールドがあり、最適化されたコードを介してそれらのフィールドすべてに最新の値が必要な場合

select * from
(select * from table_name
order by id,rev desc) temp
group by id 
4
seahawk

mySQL ではありませんが、他の人がこの質問を見つけてSQLを使用する場合、 maximum-n-per-group 問題を解決する別の方法は 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の例です

4
KyleMit

この仕事をする別の方法は、OVER PARTITION節でMAX()分析関数を使用することです。

SELECT t.*
  FROM
    (
    SELECT id
          ,rev
          ,contents
          ,MAX(rev) OVER (PARTITION BY id) as max_rev
      FROM YourTable
    ) t
  WHERE t.rev = t.max_rev 

この記事で既に文書化されている他のROW_NUMBER() OVER PARTITIONソリューションは

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

この2つのSELECTは、Oracle 10gでうまく機能します。

ROW_NUMBER()の複雑度はMAX()であり、O(n)の複雑度は最低ROW_NUMBER()であるため、MAX()ソリューションはO(n.log(n))ソリューションより確実に高速に実行されます。ここでnはテーブル内のレコード数を表します。

3
schlebe

私はこれを使うでしょう:

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句とともに使用できます。それは私の実用的な例です(あなたの問題と同じ解決をテーブル "firmy"で):

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'

それは数十から数十のレコードを持つテーブルについて尋ねられ、そしてそれはあまり強くない機械では0.01秒もかからない。

私はIN句を使用しません(それが上のどこかで言及されているように)。 INは、定数の短いリストで使用するために指定されています。副問合せに基づいて作成された問合せフィルタではありません。これは、INの副照会がスキャンされたレコードごとに実行されるため、非常に長い時間がかかるためです。

3
Marek Wysmułek

これはどう:

SELECT all_fields.*  
FROM (SELECT id, MAX(rev) FROM yourtable GROUP BY id) AS max_recs  
LEFT OUTER JOIN yourtable AS all_fields 
ON max_recs.id = all_fields.id
3
inor
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
3
guru008

これらの答えのどれも私のために働きませんでした。

これは私のために働いたものです。

with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
2
qaisjp

Revフィールドを逆の順序でソートしてから、idでグループ化したところ、各グループ化の最初の行(最も高いrev値のもの)が得られました。

SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;

http://sqlfiddle.com/ /で以下のデータをテスト済み

CREATE TABLE table1
    (`id` int, `rev` int, `content` varchar(11));

INSERT INTO table1
    (`id`, `rev`, `content`)
VALUES
    (1, 1, 'One-One'),
    (1, 2, 'One-Two'),
    (2, 1, 'Two-One'),
    (2, 2, 'Two-Two'),
    (3, 2, 'Three-Two'),
    (3, 1, 'Three-One'),
    (3, 3, 'Three-Three')
;

これにより、MySql 5.5および5.6では次の結果が得られました。 

id  rev content
1   2   One-Two
2   2   Two-Two
3   3   Three-Two
2
blokeish

これを行うにはいい方法です。

次のコードを使用してください。

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)
2
shay

レコードをある列でランク付けすることでこれを行うのが好きです。この場合、revでグループ化されたid値をランク付けします。 revが高い人は、順位が低くなります。そのため、最も高いrevのランクは1になります。

select id, rev, content
from
 (select
    @rowNum := if(@prevValue = id, @rowNum+1, 1) as row_num,
    id, rev, content,
    @prevValue := id
  from
   (select id, rev, content from YOURTABLE order by id asc, rev desc) TEMP,
   (select @rowNum := 1 from DUAL) X,
   (select @prevValue := -1 from DUAL) Y) TEMP
where row_num = 1;

変数を導入すると全体が遅くなるかどうかわからない。しかし、少なくとも私はYOURTABLEを2回問い合わせているわけではありません。

2
user5124980

これは、そのフィールドの最大値を持つフィールドだけを使ってレコードを取得するための別の解決策です。これは私が取り組んでいるプラットフォームであるSQL400にも有効です。この例では、フィールドFIELD5の最大値を持つレコードは、次のSQLステートメントによって検索されます。

SELECT A.KEYFIELD1, A.KEYFIELD2, A.FIELD3, A.FIELD4, A.FIELD5
  FROM MYFILE A
 WHERE RRN(A) IN
   (SELECT RRN(B) 
      FROM MYFILE B
     WHERE B.KEYFIELD1 = A.KEYFIELD1 AND B.KEYFIELD2 = A.KEYFIELD2
     ORDER BY B.FIELD5 DESC
     FETCH FIRST ROW ONLY)
2
Cesar

これは誰かに役立つことを願って別の解決策です 

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
2
Abdul Samad

このソリューションは、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
2
plavozont

説明

これは純粋な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データベースでテスト済み。

0
Ian A McElhenny

私は自分の問題を解決するために以下を使いました。最初に一時テーブルを作成し、一意のIDごとにmax rev値を挿入しました。

CREATE TABLE #temp1
(
    id varchar(20)
    , rev int
)
INSERT INTO #temp1
SELECT a.id, MAX(a.rev) as rev
FROM 
    (
        SELECT id, content, SUM(rev) as rev
        FROM YourTable
        GROUP BY id, content
    ) as a 
GROUP BY a.id
ORDER BY a.id

次に、これらの最大値(#temp1)を、考えられるすべてのIDとコンテンツの組み合わせに結合しました。こうすることで、最大/最大でないID /コンテンツの組み合わせを自然に除外し、それぞれに対して最大のrev値のみを残します。

SELECT a.id, a.rev, content
FROM #temp1 as a
LEFT JOIN
    (
        SELECT id, content, SUM(rev) as rev
        FROM YourTable
        GROUP BY id, content
    ) as b on a.id = b.id and a.rev = b.rev
GROUP BY a.id, a.rev, b.content
ORDER BY a.id
0
Richard Ball

revidMAX()の1つのmaxRevId値に結合してから元の値に分割すると、結合なしで選択を行うことができます。

SELECT maxRevId & ((1 << 32) - 1) as id, maxRevId >> 32 AS rev
FROM (SELECT MAX(((rev << 32) | id)) AS maxRevId
      FROM YourTable
      GROUP BY id) x;

単一のテーブルではなく複雑な結合がある場合、これは特に高速です。従来のアプローチでは、複雑な結合は2回行われます。

revidINT UNSIGNED(32ビット)で、組み合わせた値がBIGINT UNSIGNED(64ビット)に収まる場合、上記の組み合わせはビット関数で簡単です。 idおよびrevが32ビット値よりも大きい場合、または複数の列で構成されている場合は、値を結合して次のようにします。 MAX()の適切なパディングを含むバイナリ値。

0
zovio