私は文書用のこの表を持っています(簡略版はこちら):
+------+-------+--------------------------------------+
| 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、質問の支持率が回答の支持率を上回っています。これは意図していません。受け入れられた答え.
必要なのは、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-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つのスマートな動き:
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-group
にgroup-identifier
を含む2つの行がある場合、両方の方法で両方の行が結果に含まれます。
どちらのアプローチもSQL ANSI互換であるため、「フレーバー」に関係なく、お気に入りのRDBMSで機能します。
どちらの方法もパフォーマンスに優しいですが、走行距離は異なる場合があります(RDBMS、DB構造、インデックスなど)。ですから、他の方法よりも1つの方法を選択すると、 benchmark となります。そして、あなたにとって最も意味のあるものを選ぶようにしてください。
私の好みはできるだけ少ないコードを使用することです...
IN
を使ってそれを行うことができます。
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
私の考えでは、それはそれほど複雑ではありません...読みやすく保守しやすいです。
さらに別の解決策は、相関副照会を使用することです。
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個のジョインを作成するため、そのパフォーマンスはグループのサイズによって大きく左右されます。
私は答えが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
。
パフォーマンスを保証することはできませんが、Microsoft Excelの限界にヒントを得たトリックです。それはいくつかの良い機能を持っています
良いもの
_アプローチ_
これは少し醜いので、 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 の値に関係なく、既知の文字長の数にすることから始まります。
それを正しく行えば、2つの数値の文字列比較は2つの数値の数値比較と同じ "max"になるはずで、substring関数を使用して元の数値に戻すのは簡単です。どこにでも)。
私はこれが最も簡単な解決策だと思います:
SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
たまたま1行だけが必要な場合は、さらに簡単です。
SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1
また、他の目的に分解し、理解し、変更するのが最も簡単だと思います。
このアプローチを理解すると、これらの同様の問題の解決は簡単になります。最低給与の従業員の獲得(DESCをASCへ変更)、トップ10の従業員の獲得(LIMIT 1からLIMIT 10へ変更) Employee.Sorry by Employee.Commission)など.
このようなもの?
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)
これはこの問題に関して最も一般的な質問なので、ここでも同様にもう1つの回答を投稿します。
これを行うためのより簡単な方法があるように見えます(ただし MySQLの場合のみ )。
select *
from (select * from mytable order by id, rev desc ) x
group by id
ユーザーBohemianの回答を信用してください in この質問 この問題に対する簡潔でエレガントな回答を提供するための/.
編集:この解決法は多くの人に有効ですが、MySQLはGROUP BYステートメントがGROUP BYリストにない列に意味のある値を返すことを保証しないので、長期的には安定しないかもしれません。だからあなた自身の責任でこの解決策を使用してください
この問題にはNOT EXIST
ベースの解決策を使用するのが好きです。
SELECT id, rev
FROM YourTable t
WHERE NOT EXISTS (
SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)
私がこれまで言及したことがほとんどない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
の上限に早く達することもあります。
私は思う、あなたはこれが欲しいですか?
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
SQL Fiddle: ここで確認してください
Selectステートメントに多数のフィールドがあり、最適化されたコードを介してそれらのフィールドすべてに最新の値が必要な場合
select * from
(select * from table_name
order by id,rev desc) temp
group by id
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
この仕事をする別の方法は、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
はテーブル内のレコード数を表します。
私はこれを使うでしょう:
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の副照会がスキャンされたレコードごとに実行されるため、非常に長い時間がかかるためです。
これはどう:
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
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
これらの答えのどれも私のために働きませんでした。
これは私のために働いたものです。
with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
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
これを行うにはいい方法です。
次のコードを使用してください。
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)
レコードをある列でランク付けすることでこれを行うのが好きです。この場合、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回問い合わせているわけではありません。
これは、そのフィールドの最大値を持つフィールドだけを使ってレコードを取得するための別の解決策です。これは私が取り組んでいるプラットフォームである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)
これは誰かに役立つことを願って別の解決策です
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
このソリューションは、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
これは純粋な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データベースでテスト済み。
私は自分の問題を解決するために以下を使いました。最初に一時テーブルを作成し、一意の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
rev
とid
をMAX()
の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回行われます。
rev
とid
がINT UNSIGNED
(32ビット)で、組み合わせた値がBIGINT UNSIGNED
(64ビット)に収まる場合、上記の組み合わせはビット関数で簡単です。 id
およびrev
が32ビット値よりも大きい場合、または複数の列で構成されている場合は、値を結合して次のようにします。 MAX()
の適切なパディングを含むバイナリ値。