By Majid Fouladpour


2011-10-12 19:42:07 8 Comments

I have this table for documents (simplified version here):

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

How do I select one row per id and only the greatest rev?
With the above data, the result should contain two rows: [1, 3, ...] and [2, 1, ..]. I'm using MySQL.

Currently I use checks in the while loop to detect and over-write old revs from the resultset. But is this the only method to achieve the result? Isn't there a SQL solution?

Update
As the answers suggest, there is a SQL solution, and here a sqlfiddle demo.

Update 2
I noticed after adding the above sqlfiddle, the rate at which the question is upvoted has surpassed the upvote rate of the answers. That has not been the intention! The fiddle is based on the answers, especially the accepted answer.

27 comments

@Yuriy Nakonechnyy 2014-07-03 14:33:34

Since this is most popular question with regard to this problem, I'll re-post another answer to it here as well:

It looks like there is simpler way to do this (but only in MySQL):

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

Please credit answer of user Bohemian in this question for providing such a concise and elegant answer to this problem.

Edit: though this solution works for many people it may not be stable in the long run, since MySQL doesn't guarantee that GROUP BY statement will return meaningful values for columns not in GROUP BY list. So use this solution at your own risk!

@Jannes 2014-10-10 10:14:01

Except that it's wrong, as there is no guarantee that the order of the inner query means anything, nor is the GROUP BY always guaranteed to take the first encountered row. At least in MySQL and I would assume all others. In fact I was under the assumption that MySQL would simply ignore the whole ORDER BY. Any future version or a change in configuration might break this query.

@Yuriy Nakonechnyy 2014-10-10 14:41:00

@Jannes this is interesting remark :) I welcome you to answer my question providing proofs: stackoverflow.com/questions/26301877/…

@Yuriy Nakonechnyy 2014-10-10 14:59:48

@Jannes concerning GROUP BY not guaranteed to take the first encountered row - you are totally right - found this issue bugs.mysql.com/bug.php?id=71942 which asks to provide such guarantees. Will update my answer now

@Jannes 2014-10-11 19:09:10

I think I remember where I got the ORDER BY being discarded from: MySQL does that with UNIONs if you ORDER BY the inner queries, it's just ignore: dev.mysql.com/doc/refman/5.0/en/union.html says "If ORDER BY appears without LIMIT in a SELECT, it is optimized away because it will have no effect anyway." I haven't seen such a statement for the query in question here, but I don't see why it couldn't do that.

@HoldOffHunger 2016-09-14 00:28:36

I think this is the easiest solution :

SELECT *
FROM
    (SELECT *
    FROM Employee
    ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
  • SELECT * : Return all fields.
  • FROM Employee : Table searched on.
  • (SELECT *...) subquery : Return all people, sorted by Salary.
  • GROUP BY employeesub.Salary: Force the top-sorted, Salary row of each employee to be the returned result.

If you happen to need just the one row, it's even easier :

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

I also think it's the easiest to break down, understand, and modify to other purposes:

  • ORDER BY Employee.Salary DESC: Order the results by the salary, with highest salaries first.
  • LIMIT 1: Return just one result.

Understanding this approach, solving any of these similar problems becomes trivial: get employee with lowest salary (change DESC to ASC), get top-ten earning employees (change LIMIT 1 to LIMIT 10), sort by means of another field (change ORDER BY Employee.Salary to ORDER BY Employee.Commission), etc..

@Aaron J Spetner 2017-10-02 06:39:03

This does not answer the question. The question is asking how to get the data for one row (as was asked, "one row per ID") in a group query where value x is the max within each group of rows. For example a customer order table with multiple orders per customer where you want to retrieve the largest order for each customer. Your query might very well return more than one row per customer (if, for example, the two largest orders were placed by the same customer).

@HoldOffHunger 2017-10-02 12:17:12

"one row per ID" <-- keep reading, please, and you'll see "and only the greatest". That is logically equivalent to just the greatest.

@Aaron J Spetner 2017-10-03 07:12:26

Yes, but it says "and". Which means the requirements are BOTH one row per ID AND only the greatest. Using this answer will not satisfy the first requirement. Additionally, the question implies the need to retrieve a single record for ALL of the IDs. This answer requires knowledge of the number of IDs beforehand (in order to configure the LIMIT), which will require additional code. The question's goal is stated specifically as seeking a SQL-only solution. Finally, even if you know the number of unique IDs, if there are multiple occurrences of the MAX value, the LIMIT clause will be wrong.

@sba 2017-10-05 14:58:50

I did not have the exact same situation like in the original post but this is the most easy to understand and straightforward and working solution i came across so far for my problem. I am amazed how all the geeks and freaks try to overtake each other by bragging with complex / weird queries.

@HoldOffHunger 2017-11-27 16:08:24

@Aaron J Spetner: I have updated with a solution that directly addresses OP's needs.

@Raymond Nijland 2018-06-18 15:55:13

This is a hacky solution, totally busted in the later MySQL versions won't work on servers with ONLY_FULL_GROUP_BY enabled within the server config... sqlfiddle.com/#!9/215cd/4

@HoldOffHunger 2018-06-18 18:16:39

@Raymond Nijland: You can make it work by changing "*" to the fields you actually want. What is above is just a proof of concept. I modified the query to work with your mysql mode: sqlfiddle.com/#!9/215cd/6

@Paul Spiegel 2019-02-07 08:52:57

@HoldOffHunger Your last fiddle query is equivalent to SELECT DISTINCT rev FROM Table1; - sqlfiddle.com/#!9/215cd/9

@schlebe 2018-02-20 09:07:22

Another manner to do the job is using MAX() analytic function in OVER PARTITION clause

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 

The other ROW_NUMBER() OVER PARTITION solution already documented in this post is

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 

This 2 SELECT work well on Oracle 10g.

MAX() solution runs certainly FASTER that ROW_NUMBER() solution because MAX() complexity is O(n) while ROW_NUMBER() complexity is at minimum O(n.log(n)) where n represent the number of records in table !

@guru008 2017-07-30 18:12:46

SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary

@Ian A McElhenny 2019-02-22 15:18:26

Explanation

This is not pure SQL. This will use the SQLAlchemy ORM.

I came here looking for SQLAlchemy help, so I will duplicate Adrian Carneiro's answer with the python/SQLAlchemy version, specifically the outer join part.

This query answers the question of:

"Can you return me the records in this group of records (based on same id) that have the highest version number".

This allows me to duplicate the record, update it, increment its version number, and have the copy of the old version in such a way that I can show change over time.

Code

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()

Tested on a PostgreSQL database.

@inor 2013-07-14 16:09:41

How about this:

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

@Abhishek Rana 2018-12-29 11:00:18

I think, You want this?

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

SQL Fiddle : Check here

@zovio 2018-09-17 09:08:22

You can make the select without a join when you combine the rev and id into one maxRevId value for MAX() and then split it back to original values:

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;

This is especially fast when there is a complex join instead of a single table. With the traditional approaches the complex join would be done twice.

The above combination is simple with bit functions when rev and id are INT UNSIGNED (32 bit) and combined value fits to BIGINT UNSIGNED (64 bit). When the id & rev are larger than 32-bit values or made of multiple columns, you need combine the value into e.g. a binary value with suitable padding for MAX().

@KyleMit 2014-05-30 13:47:53

NOT mySQL, but for other people finding this question and using SQL, another way to resolve the problem is using Cross Apply in 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

Here's an example in SqlFiddle

@nahab 2018-02-15 13:40:14

very slow comparing to other methods - group by, windows, not exists

@Richard Ball 2018-01-05 10:51:51

I used the below to solve a problem of my own. I first created a temp table and inserted the max rev value per unique id.

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

I then joined these max values (#temp1) to all of the possible id/content combinations. By doing this, I naturally filter out the non-maximum id/content combinations, and am left with the only max rev values for each.

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

@Cesar 2017-10-16 23:48:32

Here's another solution to retrieving the records only with a field that has the maximum value for that field. This works for SQL400 which is the platform I work on. In this example, the records with the maximum value in field FIELD5 will be retrieved by the following SQL statement.

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)

@Bulat 2014-09-05 21:58:28

I like to use a NOT EXIST-based solution for this problem:

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

@EGP 2014-10-08 12:38:51

yes, not exists like this has generally been the preferred way rather than a left join. In older versions of SQL server it was faster, although i think now it makes no difference. I normally do SELECT 1 instead of SELECT *, again because in prior versions it was faster.

@qaisjp 2017-07-13 18:19:20

None of these answers have worked for me.

This is what worked for me.

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

@Abdul Samad 2017-06-20 10:10:35

here is another solution hope it will help someone

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

@topchef 2016-08-09 15:29:17

I am flabbergasted that no answer offered SQL window function solution:

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 

Added in SQL standard ANSI/ISO Standard SQL:2003 and later extended with ANSI/ISO Standard SQL:2008, window (or windowing) functions are available with all major vendors now. There are more types of rank functions available to deal with a tie issue: RANK, DENSE_RANK, PERSENT_RANK.

@mmcrae 2017-01-10 16:52:09

I think it is less intuitive and potentially less clear - but it can definitely work/be a solution.

@topchef 2017-01-10 18:22:38

intuition is tricky thing. I find it more intuitive than other answers as it builds explicit data structure that answers the question. But, again, intuition is the other side of bias...

@Rick James 2017-04-01 22:01:17

This might work in MariaDB 10.2 and MySQL 8.0.2, but not before.

@Used_By_Already 2017-12-11 00:42:33

At last, I was beginning to wonder why this wasn't here. This is far more "intuitive" than the vast majority of the "old hat" answers on this page, and way more efficient in almost all cases as it requires just a single pass of the data. Most databases now support these standard window functions (MySQL is late but will from v8 onward).

@Andrew Philips 2018-10-19 04:42:13

I had no idea this feature existed. Dug deeply into a bunch of manuals this evening. This makes so much more sense than left joins (just from a lack of frustration perspective).

@Marcin Bilski 2019-03-28 09:59:31

I recommend against using it in Bigquery; the ORDER BY clause means all data needs to be processed on a single node resulting in "Resources exceeded" for large tables.

@blokeish 2015-12-11 03:14:18

Sorted the rev field in reverse order and then grouped by id which gave the first row of each grouping which is the one with the highest rev value.

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

Tested in http://sqlfiddle.com/ with the following data

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')
;

This gave the following result in MySql 5.5 and 5.6

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

@Rick James 2017-04-01 22:02:37

This technique used to work, but no longer. See mariadb.com/kb/en/mariadb/…

@blokeish 2017-04-03 00:34:11

The original question tag is "mysql" and I have stated very clearly that my solution was tested with both Mysql 5.5 and 5.6 in sqlfiddle.com. I have provided all steps to independently verify the solution. I have not made any false claims that my solution works with Mariadb. Mariadb is not Mysql, its just a drop-in replacement for Mysql, owned by 2 different companies. Your comment will help anyone that is trying to implement it in Mariadb but my post in no way deserve a negative vote as it clearly answers the question that was asked.

@Rick James 2017-04-03 02:38:10

Yes, it works in older versions. And I have used that technique in the past, only to be burned when it stopped working. Also MySQL (in 5.7?) will also be ignoring the ORDER BY in a subquery. Since lots of people will read your answer, I am trying to steer them away from a technique that will break in their future. (And I did not give you the -1 vote.)

@philipxy 2018-09-22 11:50:21

Tests prove nothing. ORDER BY in a subquery has no guaranteed effect other than for a LIMIT in the same subquery. Even if order was preserved, the GROUP BY would not preserve it. Even if it were preserved, non-standard GROUP BY relying on disabled ONLY_FULL_GROUP_BY is specified to return some row in a group for a non-grouped column but not necessarily the first. So your query is not correct.

@Adrian Carneiro 2011-10-12 19:43:53

At first glance...

All you need is a GROUP BY clause with the MAX aggregate function:

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

It's never that simple, is it?

I just noticed you need the content column as well.

This is a very common question in SQL: find the whole data for the row with some max value in a column per some group identifier. I heard that a lot during my career. Actually, it was one the questions I answered in my current job's technical interview.

It is, actually, so common that StackOverflow community has created a single tag just to deal with questions like that: .

Basically, you have two approaches to solve that problem:

Joining with simple group-identifier, max-value-in-group Sub-query

In this approach, you first find the group-identifier, max-value-in-group (already solved above) in a sub-query. Then you join your table to the sub-query with equality on both group-identifier and 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

Left Joining with self, tweaking join conditions and filters

In this approach, you left join the table with itself. Equality, of course, goes in the group-identifier. Then, 2 smart moves:

  1. The second join condition is having left side value less than right value
  2. When you do step 1, the row(s) that actually have the max value will have NULL in the right side (it's a LEFT JOIN, remember?). Then, we filter the joined result, showing only the rows where the right side is NULL.

So you end up with:

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;

Conclusion

Both approaches bring the exact same result.

If you have two rows with max-value-in-group for group-identifier, both rows will be in the result in both approaches.

Both approaches are SQL ANSI compatible, thus, will work with your favorite RDBMS, regardless of its "flavor".

Both approaches are also performance friendly, however your mileage may vary (RDBMS, DB Structure, Indexes, etc.). So when you pick one approach over the other, benchmark. And make sure you pick the one which make most of sense to you.

@Majid Fouladpour 2011-10-12 19:56:52

The first version was much simpler, wouldn't it yeald the result by adding more columns? I also should cater for a where: select id, max(rev), content, etc., etc., from the_table where proj_id = $pid group by id

@Adrian Carneiro 2011-10-12 20:05:05

I know that MySQL allows you to add non aggregate fields to a "grouped by" query, but I find that kinda pointless. Try running this select id, max(rev), rev from YourTable group by id and you see what I mean. Take your time and try to understand it

@despot 2012-10-18 08:03:40

watch out in the subselect not to include any other column names, since they arent gonna be the ones from the max row. mysql will allow u this although other db-s like oracle will give u an exception. For most of the aggregate function it doesn't make sense although for max function would seem logical. So be careful not to trip on this ;) Cheers!

@Jason McCarrell 2012-12-12 16:31:26

First, thanks for the awesome answer. Something to add, make sure you join on what you would have grouped by for the max or min. Generally you only join by the id, but if you were grouping by name and age, then your join will be with name = name, and age = age. It took me a few minutes to realize that was my issue.

@Adrian Carneiro 2012-12-12 16:50:53

@JasonMcCarrell I'm glad this answer helped you! I get your point, this is why I called it group_identifier, which could be one or more columns. In your case, group_identifier is the combination of name and age

@Totty.js 2013-03-27 15:21:41

and how do I do when I don't have a unique identifier field per row?

@whiteatom 2013-04-06 13:41:29

Totty.. you can't.. all database tables require a primary key if you want to perform intelligent queries on them

@whiteatom 2013-04-06 13:52:51

Your second solution is a really interesting approach. On my data set the it takes 4 mins to run, vs 0.23 of a second for the subquery. Is there any indexing that you could suggest that would help this along?

@Adrian Carneiro 2013-04-07 11:28:01

@whiteatom Absolutely: indexes over id and rev (adjust names accordingly). It might be the case that an index over both should also be helpful

@whiteatom 2013-04-07 15:42:16

Hmm no dice. It took 6 mins before hand. I added the index to id and rev individually and it took 4 mins.. together it takes 3:53 and both takes 3:52. Interesting the 2 methods are that different. I wonder what circumstance the searching for row with nothing greater is faster?

@Adrian Carneiro 2013-04-08 13:16:09

@whiteatom funny, that should have done it. have you tried creating DESC index for rev? Perhaps you should post your data structure and estimated data load in a new question. Please let me know

@Lonnie Best 2013-06-08 10:08:34

Where is null is sometimes slow. Regardless, I find the 2nd solution interesting and clever. Great answer!

@Michael Lang 2013-06-24 22:42:10

How do I get it to return only one row per group though? Don't these answers return every row in each group that has a compare value equal to the maximum value? For instance, suppose there was a second row in the OP's dataset with id = 1, rev = 3. Wouldn't it return both rows with id=1, rev=3?

@Adrian Carneiro 2013-06-25 18:28:55

@MichaelLang You are talking about ties. This solution does not do tie-breaking: "If you have two rows with max-value-in-group for group-identifier, both rows will be in the result in both approaches." That's by design, since this solution is generalist. you are the one responsible for doing the tie breaker, meaning you have to decide what you are going to use as tie breaker when two max values are equal (another field perhaps?). Then you can tweak the second approach to take that into consideration.

@inor 2013-07-14 16:12:26

I am just wondering if the query I entered in my answer below is less efficient or not equivalent to the queries entered in this answer, and if so, why?

@Robert Christ 2014-01-23 13:58:16

@AdrianCarneiro so you're saying that its not possible to do tie breaking with the first version?

@Vajk Hermecz 2014-01-23 14:18:06

Worth mentioning that if you are using the code for random lookups (e.g.: as a basis of a view), the subquery based solution is painfully slow (as subquery is executed for each lookup), while the Left join solution plays nice... There would also be a correlated subquery based solution for the problem (which avoids leftjoin's problems with large group sizes). See my answer. Feel free to add it to your answer...

@ma11hew28 2014-03-14 00:29:39

@RobertChrist to arbitrarily break ties with the first version, just add DISTINCT ON (yt.id) after the initial SELECT. That made my query take twice as long though. So, I don't tie-break since ties are practically impossible in my case.

@Gherman 2014-09-18 08:24:51

Why would the first solution work? Won't max function run per each group consisting of a single row instead of all the rows as a whole.

@Chadwick Meyer 2014-11-20 22:38:09

I appreciate that second solution, because Doctrine DQL won't support subqueries in JOIN statements (like the first solution), so this is a very helpful work around!

@Nate 2014-12-19 01:02:12

@German I'm struggling to understand the query myself, but I believe the GROUP BY clause causes all rows to be returned instead of just one.

@Mikhail 2016-04-12 05:44:36

Using MariaDB here. Not sure why using HAVING does not yield correct results. i.e. SELECT a, b, max(b) as max_b FROM table GROUP BY a HAVING b=max_b. The query works but certain rows are simply missing

@rjcarr 2016-06-28 19:27:29

@adrian-carneiro : Thanks so much for your answer, but for your first solution, which I believe is supposed to be SELECT id, MAX(rev), rev, I get: "Column 'rev' must be in the GROUP BY list". Is my database lacking (not mysql), is this not standard sql, or is something else going on? Thanks!

@Akmal Salikhov 2017-02-04 14:18:49

second approach is not working to me. it display the same result as if i just run simple select from my table

@TheLogicGuy 2017-03-22 09:43:53

What is so problematic in adding the column content? why isnt that working?

@photocode 2017-11-25 03:58:28

I've used both of these solutions in the past, and for small data sets, they work fine - however - they do not scale well. Even with indexing, etc... It's far better to just break the query down into two smaller steps and populate a temp table.

@Mahesh Yadav 2018-02-15 05:26:50

Thanks!! I followed inner join approach and it is working fine.

@Hello.World 2018-07-20 15:19:34

@AdrianCarneiro I don't understand why there is equality on 2 columns instead of just 1 i.e, on ID for inner join method. Won't the method work if you join only on ID OR rev rather than both? Can someone please explain?

@Baurzhan 2018-08-08 08:25:25

Does anybody know how improve that query (especially interested in first approach) in order to return 1 row for each entity even if we have several same maximums. I.e greatest-1-per-group-when-N-greatest-exists. I suspect distint might help but it seems to be very slow

@zovio 2018-09-24 12:42:38

There is a third alternative (without JOIN) that I have not seen mentioned before: combine rev and id, select MAX() and then extract the id. Details below.

@Serhii Popov 2018-12-11 20:55:28

I tried both SQL queries on tables on 3M rows and first (1) query is much faster than second (2) one. "Much more" means I did not wait until the second request was finished. (1) SELECT a.* FROM history a JOIN (SELECT productId, MAX(updatedAt) AS updatedAt FROM history WHERE updatedAt BETWEEN '2018-12-11 00:00:00' AND '2018-12-11 23:59:58' GROUP BY productId ) AS b ON a.productId = b.productId AND a.updatedAt = b.updatedAt (2) SELECT a.* history a LEFT OUTER JOIN history b ON a.productId = b.productId AND a.updatedAt < b.updatedAt WHERE b.id IS NULL AND a.updatedAt BETWEEN '...' AND '...'

@Lu55 2018-12-17 22:47:13

NOTE for inattentive readers (to which I belong myself): despite the fact that you are looking for the MAX rev the statement should be a.rev < b.rev (at the first glance it looks as if you are looking for the minimum revision) because later you add WHERE b.rev IS NULL which gives whole command the following meaning: "There is no rev greater than the a.rev"

@Salman A 2019-02-13 12:29:49

Performance wise, the second approach is not even 50% of the first one (as far as mysql is concerned).

@seahawk 2015-09-04 05:33:22

If you have many fields in select statement and you want latest value for all of those fields through optimized code:

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

@Rick James 2017-05-17 00:48:28

This works OK for small tables, but takes 6 passes over the entire dataset, so not fast for large tables.

@Mike Viens 2018-06-01 19:07:45

This is the query I needed because there were other columns involved, too.

@user5124980 2015-07-16 18:52:31

I like to do this by ranking the records by some column. In this case, rank rev values grouped by id. Those with higher rev will have lower rankings. So highest rev will have ranking of 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;

Not sure if introducing variables makes the whole thing slower. But at least I'm not querying YOURTABLE twice.

@user5124980 2015-07-16 18:54:38

Only tried approach in MySQL. Oracle has a similar function for ranking records. Idea should work too.

@philipxy 2018-09-22 10:57:46

Reading & writing a variable in a select statement is undefined in MySQL although particular versions happen to give the answer you might expect for certain syntax involving case expressions.

@Marek Wysmułek 2015-03-04 18:12:10

I would use this:

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

Subquery SELECT is not too eficient maybe, but in JOIN clause seems to be usable. I'm not an expert in optimizing queries, but I've tried at MySQL, PostgreSQL, FireBird and it does work very good.

You can use this schema in multiple joins and with WHERE clause. It is my working example (solving identical to yours problem with table "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'

It is asked on tables having teens thusands of records, and it takes less then 0,01 second on really not too strong machine.

I wouldn't use IN clause (as it is mentioned somewhere above). IN is given to use with short lists of constans, and not as to be the query filter built on subquery. It is because subquery in IN is performed for every scanned record which can made query taking very loooong time.

@mmcrae 2017-01-10 18:52:04

I think using that subquery as a CTE might at least improve performance

@Dmitry Grekov 2018-08-10 11:37:22

Hi! For me it looks like your 1st query needs ... and o.id = t.id in the end (and the subquery should return id for that). Doesn't it?

@shay 2015-01-07 11:36:08

Here is a nice way of doing that

Use following code :

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)

@Jannes 2014-10-10 11:57:00

A third solution I hardly ever see mentioned is MySQL specific and looks like this:

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

Yes it looks awful (converting to string and back etc.) but in my experience it's usually faster than the other solutions. Maybe that just for my use cases, but I have used it on tables with millions of records and many unique ids. Maybe it's because MySQL is pretty bad at optimizing the other solutions (at least in the 5.0 days when I came up with this solution).

One important thing is that GROUP_CONCAT has a maximum length for the string it can build up. You probably want to raise this limit by setting the group_concat_max_len variable. And keep in mind that this will be a limit on scaling if you have a large number of rows.

Anyway, the above doesn't directly work if your content field is already text. In that case you probably want to use a different separator, like \0 maybe. You'll also run into the group_concat_max_len limit quicker.

@plavozont 2014-01-29 07:49:11

This solution makes only one selection from YourTable, therefore it's faster. It works only for MySQL and SQLite(for SQLite remove DESC) according to test on sqlfiddle.com. Maybe it can be tweaked to work on other languages which I am not familiar with.

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

@ma11hew28 2014-03-13 16:26:27

This doesn't appear to work for the general case. And, it doesn't work at all in PostgreSQL, returning: ERROR: column "your table.reb" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT *

@plavozont 2014-03-17 05:11:04

Sorry I didn't clarify the first time at which language it worked.

@Vajk Hermecz 2014-01-23 14:16:11

Yet another solution is to use a correlated subquery:

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

Having an index on (id,rev) renders the subquery almost as a simple lookup...

Following are comparisons to the solutions in @AdrianCarneiro's answer (subquery, leftjoin), based on MySQL measurements with InnoDB table of ~1million records, group size being: 1-3.

While for full table scans subquery/leftjoin/correlated timings relate to each other as 6/8/9, when it comes to direct lookups or batch (id in (1,2,3)), subquery is much slower then the others (Due to rerunning the subquery). However I couldnt differentiate between leftjoin and correlated solutions in speed.

One final note, as leftjoin creates n*(n+1)/2 joins in groups, its performance can be heavily affected by the size of groups...

@Doomed Mind 2017-02-02 15:27:16

This is the only one so far that worked in the way I needed it, thanks (needed to match by name, not by id)

@Pita 2017-06-05 21:13:06

I dont think this works if rev is not unique.

@Pradeep Kumar Prabaharan 2017-09-29 16:44:20

@Pita no. it works even if rev is not unique

@Jared Becksfort 2017-11-13 18:10:05

Good point for mentioning index required for simple lookup (apparently cannot plus 1 in comments anymore)

@nahab 2018-02-15 13:48:42

However I couldnt differentiate between leftjoin and correlated solutions in speed. - the same for me for Sql Server

@entonio 2018-12-05 19:34:06

I don't understand why this isn't THE solution. If nothing else, it's the only readable one.

@Kevin Burton 2011-10-12 19:47:41

My preference is to use as little code as possible...

You can do it using IN try this:

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

to my mind it is less complicated... easier to read and maintain.

@Kash 2011-11-17 17:04:49

Curious - which database engine can we use this type of WHERE clause in? This is not supported in SQL Server.

@Kevin Burton 2011-11-17 18:03:30

oracle & mysql (not sure about other databases sorry)

@lcguida 2014-01-15 17:43:39

Works on PostgreSQL too.

@coderatchet 2014-01-29 02:32:40

Confirmed working in DB2

@Marcel Pfeiffer 2014-10-26 20:32:48

Does not work with SQLite.

@Kevin Burton 2014-10-27 16:52:45

and the answer supplied is valid ANSI\ISO SQL !!!

@Arthur Collé 2015-04-04 02:54:21

Awesome, thanks. Worked in SQLDeveloper (Oracle)

@Adriel Santos 2016-04-27 12:04:50

Remember if you're using Mysql and this query returns something like SELECT list is not in GROUP BY clause and contains nonaggregated column 'db.t1.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by you need to disable the ONLY_FULL_GROUP_BY sql mode (eg: SET sql_mode = '')

@Rick James 2017-04-01 22:04:38

In MySQL, this construct performs poorly: WHERE (a,b) IN ( SELECT ... ) for two reasons -- the (a,b) and the IN(SELECT.

@Rick James 2017-05-17 01:09:01

Update: Percona 5.6.22 optimizes this in a reasonable way - Order(N), where N is number of rows in table. MySQL 5.5.43 does a terrible job - Order(N*N). MariaDB 10.0.28 does a superior job -- no table scan!

@Oriol 2017-05-18 00:59:49

What about adding an additional: GROUP BY id at the end of the query? This seems to prevent multiple records per id. Is this a safe approach?

@Hassaan Salik 2017-12-01 22:25:03

Works on spark sql too. :)

@Tal Galili 2018-04-26 08:16:58

Sadly, this doesn't yet work in Presto :(

@Mike_Jr 2018-08-17 14:58:35

Works with Mongodb as well

@David Foster 2013-06-30 06:02:30

I can't vouch for the performance, but here's a trick inspired by the limitations of Microsoft Excel. It has some good features

GOOD STUFF

  • It should force return of only one "max record" even if there is a tie (sometimes useful)
  • It doesn't require a join

APPROACH

It is a little bit ugly and requires that you know something about the range of valid values of the rev column. Let us assume that we know the rev column is a number between 0.00 and 999 including decimals but that there will only ever be two digits to the right of the decimal point (e.g. 34.17 would be a valid value).

The gist of the thing is that you create a single synthetic column by string concatenating/packing the primary comparison field along with the data you want. In this way, you can force SQL's MAX() aggregate function to return all of the data (because it has been packed into a single column). Then you have to unpack the data.

Here's how it looks with the above example, written in 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

The packing begins by forcing the rev column to be a number of known character length regardless of the value of rev so that for example

  • 3.2 becomes 1003.201
  • 57 becomes 1057.001
  • 923.88 becomes 1923.881

If you do it right, string comparison of two numbers should yield the same "max" as numeric comparison of the two numbers and it's easy to convert back to the original number using the substring function (which is available in one form or another pretty much everywhere).

@Marc B 2011-10-12 19:48:45

Something like this?

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)

@Majid Fouladpour 2011-10-12 19:51:31

The join-less ones wouldn't cut it?

@Marc B 2011-10-12 19:54:16

If they work, then they're fine too.

@Brian McCutchon 2016-06-03 05:19:36

What does WHERE yourtable do?

@Salman A 2019-02-13 12:27:13

This seems to be the fastest one (with proper indexes).

Related Questions

Sponsored Content

41 Answered Questions

[SOLVED] How to return only the Date from a SQL Server DateTime datatype

14 Answered Questions

[SOLVED] Select first row in each GROUP BY group?

27 Answered Questions

[SOLVED] Finding duplicate values in a SQL table

  • 2010-04-07 18:17:29
  • Alex
  • 2342292 View
  • 1596 Score
  • 27 Answer
  • Tags:   sql duplicates

33 Answered Questions

[SOLVED] How do I UPDATE from a SELECT in SQL Server?

34 Answered Questions

[SOLVED] Fetch the row which has the Max value for a column

25 Answered Questions

[SOLVED] How do I perform an IF...THEN in an SQL SELECT?

32 Answered Questions

[SOLVED] Reference - What does this error mean in PHP?

4 Answered Questions

[SOLVED] Inserting multiple rows in a single SQL query?

37 Answered Questions

17 Answered Questions

Sponsored Content