390

I have a table that is a collection entries as to when a user was logged on.

username, date,      value
--------------------------
brad,     1/2/2010,  1.1
fred,     1/3/2010,  1.0
bob,      8/4/2009,  1.5
brad,     2/2/2010,  1.2
fred,     12/2/2009, 1.3

etc..

How do I create a query that would give me the latest date for each user?

Update: I forgot that I needed to have a value that goes along with the latest date.

3
  • 12
    What database are you using? MySQL, SQL-Server, Oracle, ... ?
    – Peter Lang
    Commented Mar 9, 2010 at 18:49
  • 1
    Do you need the value that goes with the latest date, or the maximum value AND the maximum date? Commented Mar 9, 2010 at 18:55
  • Possible duplicate of How to get the last record per group in SQL
    – iDevlop
    Commented Aug 22, 2018 at 12:37

23 Answers 23

597

This is the simple old school approach that works with almost any db engine, but you have to watch out for duplicates:

select t.username, t.date, t.value
from MyTable t
inner join (
    select username, max(date) as MaxDate
    from MyTable
    group by username
) tm on t.username = tm.username and t.date = tm.MaxDate

Using window functions will avoid any possible issues with duplicate records due to duplicate date values, so if your db engine allows it you can do something like this:

select x.username, x.date, x.value 
from (
    select username, date, value,
        row_number() over (partition by username order by date desc) as _rn
    from MyTable 
) x
where x._rn = 1
8
  • 3
    When working with postgresql would this version be faster than using an IN (subquery) instead of the inner join?
    – TheOne
    Commented Sep 7, 2014 at 1:38
  • 3
    @TheOne as my experience, using inner join is faster than in condition Commented Dec 5, 2016 at 5:45
  • 30
    Careful with this approach: it can return more than one row per user if they have more than one record per date (max(date) would return a date that would join multiple records). To avoid this issue, it would be preferable to use @dotjoe's solution: stackoverflow.com/a/2411763/4406793.
    – Marco Roy
    Commented Mar 19, 2019 at 18:52
  • 2
    @RedFilter This worked perfect for my problem. Thanks a lot for such a technical query. By the way I used datetime instead of date to avoid getting multiple results for a particular date Commented Mar 23, 2020 at 20:52
  • 1
    why do you need the 'and t.date = tm.MaxDate' wouldn't grouping be enough?
    – duldi
    Commented Mar 25, 2020 at 18:27
216

Using window functions (works in Oracle, Postgres 8.4, SQL Server 2005, DB2, Sybase, Firebird 3.0, MariaDB 10.3)

select * from (
    select
        username,
        date,
        value,
        row_number() over(partition by username order by date desc) as rn
    from
        yourtable
) t
where t.rn = 1
6
  • 2
    Worth clarifying which Sybase product / version. It doesn't work on Sybase ASE 16. Commented Jan 16, 2019 at 19:14
  • 10
    A big benefit of this approach is that it is guaranteed to always return only one row per partition (username, in this case) and doesn't even require a unique "orderable" field (like joining on max(date) in other answers).
    – Marco Roy
    Commented Mar 19, 2019 at 19:08
  • 3
    Just to add something to what @MarcoRoy said, if you happen to have more than one record with the same max date, if you change the query, like when you are debugging it, a different record may receive a row number of 1, so the results may be inconsistent. But as long as you really don't care, then this shouldn't be a problem. This can be solved if you add the PK after the date. For example: order by date desc, id desc).
    – Andrew
    Commented Apr 26, 2019 at 21:40
  • 1
    Thanks to @Andrew, I was able to filter down to just one record by adding the PK!! In my case, I had multiple records, for the same date, which also shared the same reference id. My aim was to take the latest inserted record. I was able to do so by modifying the query above and adding order by date desc, PK_id desc) as Andrew suggested. Many thanks! Commented Feb 24, 2023 at 6:18
  • @logixplayer, I'm glad I was of help! In case the PK_id order also matches the date, you can just use order by PD_id desc. I guess the biggest id should be the latest inserted record.
    – Andrew
    Commented Feb 25, 2023 at 0:06
43

I see most of the developers use an inline query without considering its impact on huge data.

Simply, you can achieve this by:

SELECT a.username, a.date, a.value
FROM myTable a
LEFT OUTER JOIN myTable b
ON a.username = b.username 
AND a.date < b.date
WHERE b.username IS NULL
ORDER BY a.date desc;
6
  • 10
    actually this works only for duplicates, if you have more than 2 values, the condition a.date < b.date does not work, meaning, it is not a general solution, although the idea of working with the LEFT OUTER JOIN is the important thing in this answer.
    – iversoncru
    Commented Nov 6, 2018 at 12:41
  • Interestingly enough, Sybase ASE 16 works fine for smaller (<10k row) tables, but with bigger ones (>100k row) it hungs... I thought this would be the perfect example relational DBs should excel at... Commented Jan 16, 2019 at 19:21
  • 1
    @levantpied... Yeah left join is costly on larger datasets. You can tweak a performance by putting filter condition on join itself to handle it to some way if possible.
    – sujeet
    Commented Jan 22, 2019 at 10:44
  • One thing I don't understand is the IS NULL part: If the table self join based on username (a.username = b.username) then how would we find b.username is NULL in the result window? Commented Jul 26, 2021 at 3:09
  • 1
    @KatherineChen For the record in a which has the max date, there won't be any record in b which is greater.
    – jsfan
    Commented Aug 4, 2021 at 9:57
43

From my experience the fastest way is to take each row for which there is no newer row in the table.

Another advantage is that the syntax used is very simple, and that the meaning of the query is rather easy to grasp (take all rows such that no newer row exists for the username being considered).

NOT EXISTS

SELECT username, value
FROM t
WHERE NOT EXISTS (
  SELECT *
  FROM t AS witness
  WHERE witness.username = t.username AND witness.date > t.date
);

ROW_NUMBER

SELECT username, value
FROM (
  SELECT username, value, row_number() OVER (PARTITION BY username ORDER BY date DESC) AS rn
  FROM t
) t2
WHERE rn = 1

INNER JOIN

SELECT t.username, t.value
FROM t
INNER JOIN (
  SELECT username, MAX(date) AS date
  FROM t
  GROUP BY username
) tm ON t.username = tm.username AND t.date = tm.date;

LEFT OUTER JOIN

SELECT username, value
FROM t
LEFT OUTER JOIN t AS w ON t.username = w.username AND t.date < w.date
WHERE w.username IS NULL
5
  • I'm having difficulties understanding the NOT EXISTS version. Aren't you missing an aggregation in the subquery part? If I run this on my table I only get back 3 employee records from 40 employees that I have in the table. I should be getting at least 40 records. In the inner query, shouldn't we also be matching by username?
    – Narshe
    Commented Oct 25, 2019 at 9:11
  • It works for me using the following: SELECT username, value FROM t WHERE NOT EXISTS ( SELECT * FROM t AS witness WHERE witness.date > t.date AND witness.username = t.username );
    – Narshe
    Commented Oct 25, 2019 at 9:26
  • I have looked at the NOT EXISTS and it looks to only return the higher entry for all users as opposed to: "a query that would give me the latest date for each user". Commented Feb 10, 2020 at 19:17
  • You are right indeed, I update my query. Thanks for your remark! @Narshe sorry I missed up your comments for some reason :/ But you are absolutely right. Commented Feb 12, 2020 at 7:14
  • This works really nicely, so simple. Thank you!
    – Christoph
    Commented Nov 10, 2021 at 13:44
31

To get the whole row containing the max date for the user:

select username, date, value
from tablename where (username, date) in (
    select username, max(date) as date
    from tablename
    group by username
)
3
  • 2
    Working for MySQL
    – School Boy
    Commented Nov 26, 2016 at 12:52
  • 2
    Beware that this will give you duplicates if there is more than one record with the same date for a specific user. You may or may not want this.
    – Andrew
    Commented Apr 26, 2019 at 21:43
  • This sql is slow in Oracle with in clause, it will not use the index
    – meadlai
    Commented Oct 10, 2019 at 3:01
12
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
2
  • 7
    While this is another possible solution, this is not normally a good way to solve this. Doing it this way will cause the inner query to run once for each name in the table, causing a major slowdown for any table of significant size. Doing a separate query that does not have a element from the first query in the where clause then having the two tables joined will usually be faster. Commented Jun 3, 2013 at 14:49
  • This does have the nice feature of being one of the more understandable solutions that isn't implementation specific. Commented Apr 20, 2018 at 22:15
3

This one should give you the correct result for your edited question.

The sub-query makes sure to find only rows of the latest date, and the outer GROUP BY will take care of ties. When there are two entries for the same date for the same user, it will return the one with the highest value.

SELECT t.username, t.date, MAX( t.value ) value
FROM your_table t
JOIN (
       SELECT username, MAX( date ) date
       FROM your_table
       GROUP BY username
) x ON ( x.username = t.username AND x.date = t.date )
GROUP BY t.username, t.date
0
3

If your database syntax supports it, then TOP 1 WITH TIES can be a lifesafer in combination with ROWNUMER.

With the example data you provided, use this query:

SELECT TOP 1 WITH TIES
  username, date, value
FROM user_log_in_attempts
ORDER BY ROW_NUMBER() OVER (PARTITION BY username ORDER BY date DESC)

It yields:

username | date      | value
-----------------------------
bob      | 8/4/2009  | 1.5
brad     | 2/2/2010  | 1.2
fred     | 12/2/2009 | 1.3

Demo

How it works:

  • ROWNUMBER() OVER (PARTITION BY... ORDER BY...) For each username a list of rows is calculated from the youngest (rownumber=1) to the oldest (rownumber=high)
  • ORDER BY ROWNUMBER... sorts the youngest rows of each user to the top, followed by the second-youngest rows of each user, and so on
  • TOP 1 WITH TIES Because each user has a youngest row, those youngest rows are equal in the sense of the sorting criteria (all have rownumber=1). All those youngest rows will be returned.

Tested with SQL-Server.

1
  • 1
    But the latest result for fred is date=1/3/2010 and value=1.0 Commented Nov 8, 2021 at 9:38
1
SELECT DISTINCT Username, Dates,value 
FROM TableName
WHERE  Dates IN (SELECT  MAX(Dates) FROM TableName GROUP BY Username)


Username    Dates       value
bob         2010-02-02  1.2       
brad        2010-01-02  1.1       
fred        2010-01-03  1.0       
2
  • This probably wouldn't work if multiple users had orders on the same date; what if brad and bob both had an order on January 2nd?
    – AHiggins
    Commented Jul 27, 2015 at 14:42
  • I am grouping by username so it will work and the results will be like this: Username Dates value bob 2010-02-02 1.2 brad 2010-02-02 1.4 fred 2010-01-03 1.0
    – wara
    Commented Jul 28, 2015 at 11:57
1

This is similar to one of the answers above, but in my opinion it is a lot simpler and tidier. Also, shows a good use for the cross apply statement. For SQL Server 2005 and above...

select
    a.username,
    a.date,
    a.value,
from yourtable a
cross apply (select max(date) 'maxdate' from yourtable a1 where a.username=a1.username) b
where a.date=b.maxdate
1

You could also use analytical Rank Function

    with temp as 
(
select username, date, RANK() over (partition by username order by date desc) as rnk from t
)
select username, rnk from t where rnk = 1
1
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
1
  • 3
    While this code may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply. From Review Commented May 20, 2020 at 15:47
1

Here's one way to return only the most recent record for each user in SQL Server:

WITH CTE AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) AS rn
  FROM your_table
)
SELECT *
FROM CTE
WHERE rn = 1;

This uses a common table expression (CTE) to assign a unique rn (row number) to each record for each user, based on the user_id and sorted in descending order by date. The final query then selects only the records with rn equal to 1, which represents the most recent record for each user.

0
SELECT Username, date, value
 from MyTable mt
 inner join (select username, max(date) date
              from MyTable
              group by username) sub
  on sub.username = mt.username
   and sub.date = mt.date

Would address the updated problem. It might not work so well on large tables, even with good indexing.

0
SELECT *
FROM ReportStatus c
inner join ( SELECT 
  MAX(Date) AS MaxDate
  FROM ReportStatus ) m
on  c.date = m.maxdate
0

For Oracle sorts the result set in descending order and takes the first record, so you will get the latest record:

select * from mytable
where rownum = 1
order by date desc
0
0
SELECT t1.username, t1.date, value
FROM MyTable as t1
INNER JOIN (SELECT username, MAX(date)
            FROM MyTable
            GROUP BY username) as t2 ON  t2.username = t1.username AND t2.date = t1.date
1
  • 4
    A sentence or two on implementation or explanation goes a long way toward creating a quality answer.
    – user4039065
    Commented Dec 2, 2014 at 21:32
0

Select * from table1 where lastest_date=(select Max(latest_date) from table1 where user=yourUserName)

Inner Query will return the latest date for the current user, Outer query will pull all the data according to the inner query result.

0

I used this way to take the last record for each user that I have on my table. It was a query to get last location for salesman as per recent time detected on PDA devices.

CREATE FUNCTION dbo.UsersLocation()
RETURNS TABLE
AS
RETURN
Select GS.UserID, MAX(GS.UTCDateTime) 'LastDate'
From USERGPS GS
where year(GS.UTCDateTime) = YEAR(GETDATE()) 
Group By GS.UserID
GO
select  gs.UserID, sl.LastDate, gs.Latitude , gs.Longitude
        from USERGPS gs
        inner join USER s on gs.SalesManNo = s.SalesmanNo 
        inner join dbo.UsersLocation() sl on gs.UserID= sl.UserID and gs.UTCDateTime = sl.LastDate 
        order by LastDate desc
0

My small compilation

  • self join better than nested select
  • but group by doesn't give you primary key which is preferable for join
  • this key can be given by partition by in conjunction with first_value (docs)

So, here is a query:

select
 t.*
from 
 Table t inner join (
  select distinct first_value(ID) over(partition by GroupColumn order by DateColumn desc) as ID
  from Table
  where FilterColumn = 'value'
 ) j on t.ID = j.ID

Pros:

  • Filter data with where statement using any column
  • select any columns from filtered rows

Cons:

  • Need MS SQL Server starting with 2012.
0

I did somewhat for my application as it:

Below is the query:

select distinct i.userId,i.statusCheck, l.userName from internetstatus 
as i inner join login as l on i.userID=l.userID 
where nowtime in((select max(nowtime) from InternetStatus group by userID));    
-1
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
3
  • Welcome to StackOverflow and thanks for attempting to help. Code-only answers like yours are less appreciated when compared to answers which explain the solution.
    – Yunnosch
    Commented May 29, 2017 at 11:34
  • Please read this how-to-answer for providing quality answer. Commented May 29, 2017 at 11:44
  • 1
    and. it does not return to MAX for each username, just to latest single row. Commented Aug 15, 2018 at 4:57
-10

You would use aggregate function MAX and GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
2
  • 9
    Your edit will only pick a random value, not the one associated with the MAX(date) row.
    – Alison R.
    Commented Mar 9, 2010 at 18:55
  • 2
    it will give the max date but the username and value may not be of same record.
    – S K R
    Commented Apr 20, 2020 at 9:47

Not the answer you're looking for? Browse other questions tagged or ask your own question.