title: PostgreSQL - fetch the rows which have the Max value for a column in each GROUP BY group
body: <p>I'm dealing with a Postgres table (called "lives") that contains records with columns for time_stamp, usr_id, transaction_id, and lives_remaining. I need a query that will give me the most recent lives_remaining total for each usr_id<br></p>
  
  <ol>
  <li>There are multiple users (distinct usr_id's)<br></li>
  <li>time_stamp is not a unique identifier: sometimes user events (one by row in the table) will occur with the same time_stamp.<br></li>
  <li>trans_id is unique only for very small time ranges: over time it repeats<br></li>
  <li>remaining_lives (for a given user) can both increase and decrease over time</li>
  </ol>
  
  <p>example:</p>
  
  <pre>time_stamp|lives_remaining|usr_id|trans_id
  -----------------------------------------
    07:00  |       1       |   1  |   1    
    09:00  |       4       |   2  |   2    
    10:00  |       2       |   3  |   3    
    10:00  |       1       |   2  |   4    
    11:00  |       4       |   1  |   5    
    11:00  |       3       |   1  |   6    
    13:00  |       3       |   3  |   1    
  </pre>
  
  <p>As I will need to access other columns of the row with the latest data for each given usr_id, I need a query that gives a result like this:</p>
  
  <pre>time_stamp|lives_remaining|usr_id|trans_id
  -----------------------------------------
    11:00  |       3       |   1  |   6    
    10:00  |       1       |   2  |   4    
    13:00  |       3       |   3  |   1    
  </pre>
  
  <p>As mentioned, each usr_id can gain or lose lives, and sometimes these timestamped events occur so close together that they have the same timestamp! Therefore this query won't work:</p>
  
  <pre><code>SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM 
        (SELECT usr_id, max(time_stamp) AS max_timestamp 
         FROM lives GROUP BY usr_id ORDER BY usr_id) a 
  JOIN lives b ON a.max_timestamp = b.time_stamp
  </code></pre>
  
  <p>Instead, I need to use both time_stamp (first) and trans_id (second) to identify the correct row. I also then need to pass that information from the subquery to the main query that will provide the data for the other columns of the appropriate rows. This is the hacked up query that I've gotten to work:</p>
  
  <pre><code>SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM 
        (SELECT usr_id, max(time_stamp || '*' || trans_id) 
         AS max_timestamp_transid
         FROM lives GROUP BY usr_id ORDER BY usr_id) a 
  JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id 
  ORDER BY b.usr_id
  </code></pre>
  
  <p>Okay, so this works, but I don't like it. It requires a query within a query, a self join, and it seems to me that it could be much simpler by grabbing the row that MAX found to have the largest timestamp and trans_id. The table "lives" has tens of millions of rows to parse, so I'd like this query to be as fast and efficient as possible. I'm new to RDBM and Postgres in particular, so I know that I need to make effective use of the proper indexes. I'm a bit lost on how to optimize.</p>
  
  <p>I found a similar discussion <a href="https://stackoverflow.com/questions/121387/sql-fetch-the-row-which-has-the-max-value-for-a-column">here</a>. Can I perform some type of Postgres equivalent to an Oracle analytic function?</p>
  
  <p>Any advice on accessing related column information used by an aggregate function (like MAX), creating indexes, and creating better queries would be much appreciated!</p>
  
  <p>P.S. You can use the following to create my example case:</p>
  
  <pre><code>create TABLE lives (time_stamp timestamp, lives_remaining integer, 
                      usr_id integer, trans_id integer);
  insert into lives values ('2000-01-01 07:00', 1, 1, 1);
  insert into lives values ('2000-01-01 09:00', 4, 2, 2);
  insert into lives values ('2000-01-01 10:00', 2, 3, 3);
  insert into lives values ('2000-01-01 10:00', 1, 2, 4);
  insert into lives values ('2000-01-01 11:00', 4, 1, 5);
  insert into lives values ('2000-01-01 11:00', 3, 1, 6);
  insert into lives values ('2000-01-01 13:00', 3, 3, 1);
  </code></pre>