369

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.

fishhead
  • 5,829
  • 7
  • 32
  • 43

23 Answers23

568

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
D'Arcy Rittich
  • 167,292
  • 40
  • 290
  • 283
  • 3
    When working with postgresql would this version be faster than using an IN (subquery) instead of the inner join? – TheOne Sep 07 '14 at 01:38
  • 3
    @TheOne as my experience, using inner join is faster than in condition – dada Dec 05 '16 at 05: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: https://stackoverflow.com/a/2411763/4406793. – Marco Roy Mar 19 '19 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 – Muhammad Khan Mar 23 '20 at 20:52
  • 1
    why do you need the 'and t.date = tm.MaxDate' wouldn't grouping be enough? – duldi Mar 25 '20 at 18:27
  • 1
    @duldi grouping will not be able to show ```t.value``` since only aggregate and group by values can be shown in the query result. – AnandShiva Dec 27 '21 at 05:04
  • Beware of self-joins. They are unsuitable if the groups are large, because for each group, they combine each row in that group with each other row in that group. This is quadratic in the group size. A group of 1000 rows will explode to a million. This (possibly absurd) set of rows is _only filtered out at the end_, after it has already been worked on. This is easily observed against two large data sets of the same total size, one with many results (small groups), the other with few results (large groups). The query with larger groups will be far, far slower, despite returning fewer rows. – Timo Aug 14 '23 at 17:15
209

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
dotjoe
  • 26,242
  • 5
  • 63
  • 77
  • 2
    Worth clarifying which Sybase product / version. It doesn't work on Sybase ASE 16. – levant pied Jan 16 '19 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 Mar 19 '19 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 Apr 26 '19 at 21:40
  • 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! – logixplayer Feb 24 '23 at 06: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 Feb 25 '23 at 00: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;
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
sujeet
  • 587
  • 5
  • 8
  • 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 Nov 06 '18 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... – levant pied Jan 16 '19 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 Jan 22 '19 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? – Katherine Chen Jul 26 '21 at 03: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 Aug 04 '21 at 09:57
  • @KatherineChen and to myself: without that line, we will have a table of rows of two types: a.date, a.username, a.value, b.date, b.username, b.value (type 1) a.date, a.username, a.value, empty, empty, empty (type 2). type1 are rows with a.date< b.date and a.username = b.username. Type2 are rows that has no b such that a.date – user44875 Mar 02 '22 at 11:40
39

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
Fabian Pijcke
  • 2,920
  • 25
  • 29
  • 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 Oct 25 '19 at 09: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 Oct 25 '19 at 09: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". – Tasos Zervos Feb 10 '20 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. – Fabian Pijcke Feb 12 '20 at 07:14
  • This works really nicely, so simple. Thank you! – Christoph Nov 10 '21 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
)
Alison R.
  • 4,204
  • 28
  • 33
12
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
Hugo Dozois
  • 8,147
  • 12
  • 54
  • 58
Manix
  • 121
  • 1
  • 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. – Scott Chamberlain Jun 03 '13 at 14:49
  • This does have the nice feature of being one of the more understandable solutions that isn't implementation specific. – Michael Szczepaniak Apr 20 '18 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
Peter Lang
  • 54,264
  • 27
  • 148
  • 161
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.

slartidan
  • 20,403
  • 15
  • 83
  • 131
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       
GrandMasterFlush
  • 6,269
  • 19
  • 81
  • 104
wara
  • 31
  • 3
  • 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 Jul 27 '15 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 Jul 28 '15 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
imba22
  • 651
  • 1
  • 13
  • 25
1
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
Chris Catignani
  • 5,040
  • 16
  • 42
  • 49
  • 3
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) 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](/review/late-answers/26180969) – double-beep May 20 '20 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.

Eli Nunez
  • 589
  • 5
  • 10
0
SELECT *
FROM ReportStatus c
inner join ( SELECT 
  MAX(Date) AS MaxDate
  FROM ReportStatus ) m
on  c.date = m.maxdate
kleopatra
  • 51,061
  • 28
  • 99
  • 211
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.

Philip Kelley
  • 39,426
  • 11
  • 57
  • 92
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
Mike G
  • 4,232
  • 9
  • 40
  • 66
David
  • 51
  • 2
  • 4
    A sentence or two on implementation or explanation goes a long way toward creating a quality answer. –  Dec 02 '14 at 21:32
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
skuntsel
  • 11,624
  • 11
  • 44
  • 67
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
Bugs
  • 4,491
  • 9
  • 32
  • 41
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.
resnyanskiy
  • 1,607
  • 1
  • 24
  • 24
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));    
Hardik Shah
  • 4,042
  • 2
  • 20
  • 41
Sajee
  • 1
-1
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
Floern
  • 33,559
  • 24
  • 104
  • 119
AJAY
  • 17
  • 1
  • 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 May 29 '17 at 11:34
  • Please read this [how-to-answer](http://stackoverflow.com/help/how-to-answer) for providing quality answer. – thewaywewere May 29 '17 at 11:44
  • 1
    and. it does not return to MAX for each username, just to latest single row. – IrvineCAGuy Aug 15 '18 at 04:57
-9

You would use aggregate function MAX and GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
Matthew Jones
  • 25,644
  • 17
  • 102
  • 155