简体   繁体   中英

How to grab the last three rows in MySQL

My Objective is to grab the last few rows (I'm trying three) from a table for each account. I have no trouble getting the first rows but I'm having trouble making the top three most recent rows. For example, let's say I have the following table:

+--------+------------+------------+-----------+
| rownum | entryDate  | particular | accountID |
+--------+------------+------------+-----------+
|      1 | 2015-10-01 | Item1      |         1 |
|      2 | 2015-10-01 | Item2      |         1 |
|      3 | 2015-10-02 | Item3      |         1 |
|      4 | 2015-10-02 | Item4      |         1 |
|      5 | 2015-10-02 | Item5      |         2 |
|      6 | 2015-10-03 | Item6      |         2 |
|      7 | 2015-10-05 | Item7      |         3 |
+--------+------------+------------+-----------+

What I'm trying to get is:

+--------+------------+------------+-----------+
| rownum | entryDate  | particular | accountID |
+--------+------------+------------+-----------+
|      4 | 2015-10-02 | Item4      |         1 |
|      3 | 2015-10-02 | Item3      |         1 |
|      2 | 2015-10-01 | Item2      |         1 |
|      6 | 2015-10-03 | Item6      |         2 |
|      5 | 2015-10-02 | Item5      |         2 |
|      7 | 2015-10-05 | Item7      |         3 |
+--------+------------+------------+-----------+

Where Item1 was removed because it already had 3 before it. I've tried the following code, but it doesn't retrieve the most recent

select rownum, entryDate, particular, accountID
from (
    select entryDate, particular, accountID
    @rownum := if(@account = accountID, @rownum + 1, 1) rownum,
    @account := accountID 
    from entries 
    join ( select @rownum := 0, @account := 0 ) init
    order by accountID, entryDate desc) t
where t.rownum <= 3 -- Limits the rows per account

If anyone could point me in the right direction that'd be great!

EDIT: However, what I'm retrieving as a result is:

+--------+------------+------------+-----------+
| rownum | entryDate  | particular | accountID |
+--------+------------+------------+-----------+
|      3 | 2015-10-02 | Item3      |         1 |
|      2 | 2015-10-01 | Item2      |         1 |
|      1 | 2015-10-01 | Item1      |         1 |
|      6 | 2015-10-03 | Item6      |         2 |
|      5 | 2015-10-02 | Item5      |         2 |
|      7 | 2015-10-05 | Item7      |         3 |
+--------+------------+------------+-----------+

Also, if it helps, I've tested this on MySQL workbench, SQLbuddy, PHP (the web application), and PHPMyAdmin and they all produce the same result

A safe way to write this query is:

select rownum, entryDate, particular, accountID
from (select entryDate, particular, accountID,
             (@rownum := if(@account = accountID, @rownum + 1, 
                            if(@account := accountID, 1, 1)
                           )
             ) as rownum,
      from entries cross join
           (select @rownum := 0, @account := 0 ) init
      order by accountid, entryDate desc
     ) t
where t.rownum <= 3;

This is explained in the documentation :

As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement . . . In the following statement, you might think that MySQL will evaluate @a first and then do an assignment second:

 SELECT @a, @a:=@a+1, ...; 

However, the order of evaluation for expressions involving user variables is undefined.

Your query works for me if I fix the syntax error.

select rownum, entryDate, particular, accountID
from (
    select entryDate, particular, accountID,
    @rownum := if(@account = accountID, @rownum + 1, 1) rownum,
    @account := accountID 
    from entries 
    join ( select @rownum := 0, @account := 0 ) init
    ORDER BY accountid, entryDate desc
) t
where t.rownum <= 3;

rownum  entryDate            particular         accountID
1       October, 02 2015     Item3              1
2       October, 02 2015     Item4              1
3       October, 01 2015     Item1              1
1       October, 03 2015     Item6              2
2       October, 02 2015     Item5              2
1       October, 05 2015     Item7              3

Note that Item1 and Item2 have the same entryDate. It's not predictable whether you'll get Item1, 3 and 4 or 2, 3 and 4. However, you should never get Item1, 2 and 3.

Also note that rownum is not 1 through 7, but incremented separately for each account. This is correct. That your output differs for the same query. I think I know what happened.

Your very clever subquery relies on the order of operations of columns in the sub-select. In particular, this.

    @rownum := if(@account = accountID, @rownum + 1, 1) rownum,
    @account := accountID

That's the trick which assigns a separate set of rownums to each account. It relies on the fact that the rows are ordered by account and that @rownum is being set before @account .

Problem is, I don't think you can rely on that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM