Question

I have the following table, where the * marked columns makes up the primary key. They are basically rowing sessions with the distance rowed (500, 1000, etc)and the time it took in milliseconds.

* user_id
* season
* n
  date
  meters
  time

I need to find the personal bests for a particular user, so the lowest time for each distance and the date it was rowed.

I've been trying to figure it out, but with all the sub-queries, joining and grouping I'm just... failing hard. Anyone able to help out an SQL newb?


My final solution based on the accepted answer. Tried to simplify it a bit since I know in advance which user it's for. There is also clause for picking out only standard distances. Seems to work as far as I can see, but if you see anything fishy, please let me know!

SELECT
    MIN(x.date) "date", 
    x.meters, 
    x.time
FROM session x
INNER JOIN (SELECT
            z.date,
            z.meters,
            MIN(z.time) "time"
        FROM session z
        WHERE z.user_id = :user_id
          AND z.meters IN (500,1000,2000,5000,6000,10000,21097,42195,100000)
        GROUP BY z.meters) y 
    ON x.meters = y.meters AND x.time = y.time
GROUP BY x.meters, x.time
Was it helpful?

Solution

OK, try this.

(1)

    SELECT x.date, x.user_id, x.meters, x.time
    FROM
    MyTable x

    JOIN
    (
        SELECT y.user_id, y.meters, min(y.time) AS time
        FROM
        MyTable y
        GROUP BY y.user_id, y.meters
    ) t ON x.user_id = t.user_id AND x.time = t.time AND x.meters = t.meters

This is a pattern of some sort, I've seen it used by DBAs very often.

But this above will give you >=2 rows per user, per distance
(if the user achieved his best time more than once).
If you want e.g. the first date on which he achieved his
best result then you can try this second query below.
In (2) basically the query (1) is used as subquery.

(2)

SELECT min(m.date), m.user_id, m.meters, m.time

FROM

    (

        SELECT x.date, x.user_id, x.meters, x.time
        FROM
        MyTable x

        JOIN
        (
            SELECT y.user_id, y.meters, min(y.time) AS time
            FROM
            MyTable y
            GROUP BY y.user_id, y.meters
        ) t ON x.user_id = t.user_id AND x.time = t.time AND x.meters = t.meters

    ) m

GROUP BY m.user_id, m.meters, m.time

OK, and now I tested both queries.

So now I'm relaxed as I indeed didn't lie to you :) with any of the two queries.

Simplified version of (2) (for a particular user) would be this one.

(3)

SELECT min(x.date), x.user_id, x.meters, x.time
FROM
MyTable x

JOIN
(
   SELECT y.user_id, y.meters, min(y.time) AS time
   FROM
   MyTable y
   where y.user_id = 1
   GROUP BY y.user_id, y.meters
) t ON x.user_id = t.user_id AND x.time = t.time AND x.meters = t.meters

GROUP BY x.user_id, x.meters, x.time

(4)

Here is the schema and the data I tested my queries on.

create table MyTable(user_id int, season int, n int, date datetime, meters int, time int);

insert into MyTable values (1, 1, 10, '2010-01-08', 500, 10);
insert into MyTable values (1, 1, 20, '2010-01-10', 500, 10);

insert into MyTable values (1, 1, 30, '2010-01-10', 1000, 50);
insert into MyTable values (1, 1, 40, '2010-02-10', 1000, 50);
insert into MyTable values (1, 1, 50, '2010-03-10', 1000, 70);

insert into MyTable values (1, 1, 60, '2010-03-10', 5000, 20);
insert into MyTable values (1, 1, 70, '2010-04-10', 5000, 20);
insert into MyTable values (1, 1, 80, '2010-05-10', 5000, 50);

insert into MyTable values (2, 1, 90, '2010-01-10', 500, 10);
insert into MyTable values (2, 1, 100, '2010-01-08', 500, 10);

insert into MyTable values (2, 1, 110, '2010-01-11', 1000, 50);
insert into MyTable values (2, 1, 120, '2010-02-11', 1000, 50);
insert into MyTable values (2, 1, 130, '2010-03-11', 1000, 70);

insert into MyTable values (2, 1, 140, '2010-05-11', 5000, 20);
insert into MyTable values (2, 1, 150, '2010-06-11', 5000, 20);
insert into MyTable values (2, 1, 160, '2010-07-11', 5000, 50);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top