I have a problem with my mysql query.
My database:
sections
id
section_name
grades
id
user_id
section_id
date
grade
I want my data
My solution involves the use of subquery to get the id of the latest grade and then passing it up to the query which then joins both sections and grades table to return section_name and grade. Please consider this more as a psuedocode than a valid sql query as I dont have time to test it. Might come back later to edit it
SELECT section_name, grade FROM sections, grades WHERE sections.id = grades.id AND grades.id = (SELECT id FROM grades WHERE section_id = '$Id' ORDER by date DESC LIMIT 1)
SELECT sections.section_name, grades.grade
FROM
grades,
sections
WHERE
sections.id = grades.section_id
AND
grades.user_id = '.$id.'
ORDER BY grades.date DESC
LIMIT 1
try this?
Lets assume we are looking for grades of user with id=1 (just to test query without php). You can replace it with '$Id' later.
If your date column is precise enough to ensure it is unique for every grade you can use:
SELECT s.section_name, g1.grade FROM
( SELECT g.section_id, max(g.date) AS last_grade_date
FROM grades g
WHERE g.user_id = 1
GROUP BY g.section_id ) gd
JOIN grades g1
ON g1.date = gd.last_grade_date
JOIN sections s
ON s.id = gd.section_id
If your date is not unique then you have to join grades back to itself on id found in dependent subquery:
SELECT s.section_name, ga.grade FROM
( SELECT g1.section_id, max(g1.date) AS last_grade_date
FROM grades g1
WHERE g1.user_id = 1
GROUP BY g1.section_id ) gmax
JOIN grades ga
ON ga.id =
( SELECT g2.id
FROM grades g2
WHERE g2.user_id = 1
AND g2.section_id = gmax.section_id
AND g2.date = gmax.last_grade_date
LIMIT 1 )
JOIN sections s
ON s.id = gmax.section_id
You need index on (user_id, section_id, date) for this query.
ALTER TABLE grades ADD INDEX user_section_date( user_id, section_id, date ) ;
@comment: Ok I'll try to explain the second query as it gives correct results for any case.
In table g1 we take rows from grades for user with id=1, we group them by section and in every section we find a maximum date - which means most recent date. At this point we don't know yet which row exactly holds this most recent date, because we can select only columns that are in the group by
or aggregate functions (like max()). Mysql allows to select other columns, like grade, called hidden columns (other dbs would just throw a syntax error), but is free to return any row from each group, usually not the one we want, and we want the one holding most recent date. If all rows have the same value, like user_id in this case, its ok, but we need grades, which can be different in every group. For small tables select can return the right one, thats why some people claim ordering by date can help, because they test it on small tables and see correct results, but it goes wrong once the table grows, there are row updates, deletes and so on.
Basically we now have a list of sections and most recent dates and we need to find out grades. So we have to join this g1 table we just got to the grades table to find a row that holds the most recent date for every section. Id is the only column we are sure to be unique (if we don't join on unique column or unique list of columns we'll get more rows and we want exactly one) so we try to find this id in dependent subquery g2 (its a subquery that references values from outside, from gmax in this case, which is just an alias for g1, explained earlier).
Once we have grade for every section_id the only thing left to do is to join that to sections table on section_id to get section_name instead of its id value.
If I get this right you want the latest grade per section. This cannot be done simply with MySQL.
You'd need a window function or a sequence generator, neither of which are available in MySQL. There is a workaround using variables, however. See this question for a similar use-case:
MySQL query: Using UNION and getting row number as part of SELECT
Additional background information here:
SQL/mysql - Select distinct/UNIQUE but return all columns?
What you'll likely end up doing is a big nested query that will look like this:
select t.*
from (select @rownum := @rownum + 1 rownum,
t.*
from (select *
from grades join sections on sections.id = grades.section_id
where user_id=$id order by grades.date desc
) t,
(select @rownum := 0) r
) t
where t.rownum = 1
I recently had to do something similiar, but with T-SQL so you'll have to change it yourself to get it to work in MySQL (which I don't have installed here so I can't check for you I'm afraid).
Add to the WHERE clause:
AND grades.date = (SELECT TOP 1 date
FROM grades
WHERE date > GETDATE()
ORDER BY ABS(CONVERT(FLOAT,GETDATE() - date)))
GETDATE()
is equivalent to NOW()
, TOP 1
is like LIMIT 1
and the other functions, well I don't know the MySQL equivalents off the top of my head!
SELECT grades.*,sections.*
FROM grades inner join sections on grades.sections_id = sections.id
where grades.user_id = '.$id.'
GROUP BY grades.section_id
ORDER BY grades.date DESC
you can use this it is working 100%