In Postgresql
(version 10) , following sql select all rows order by the avg_grade
.
-- query - students list, order by average grade,
select s.student_id, s.student_name, avg(ce.grade) as avg_grade
from students as s
left join course_enrollment as ce on s.student_id = ce.student_id
group by s.student_id
order by avg_grade desc NULLS LAST;
Relevant tables
students:
create table students (
student_id bigserial not null primary key,
student_name varchar(200) not null,
created timestamp default CURRENT_TIMESTAMP not null
);
course_enrollment:
-- create table,
create table course_enrollment
(
course_id bigint not null,
student_id bigint not null,
grade float not null,
created timestamp default CURRENT_TIMESTAMP not null,
unique (course_id, student_id)
);
Questions:
avg_grade
have the highest values? BTW:
window functions
. group by
is required. I would use a subquery:
select student_id, student_name, avg_grade, rank() over (order by avg_grade desc)
from (select s.student_id,
s.student_name,
avg(ce.grade) as avg_grade,
rank() over (order by avg(ce.grade) desc nulls last) as seqnum,
count(*) over () as cnt
from students s
left join
course_enrollment ce
on s.student_id = ce.student_id
group by s.student_id
) as ce_avg
where seqnum <= cnt * 0.1;
There are other window functions you can use instead, such as NTILE()
and PERCENTILE_DISC()
. I prefer the direct calculation because it gives more control over how ties are handled.
After trying for a while, got a ugly yet working solution by myself.
select *, rank() over (order by avg_grade desc)
from (
select s.student_id, s.student_name, avg(ce.grade) as avg_grade
from students as s
left join course_enrollment as ce on s.student_id = ce.student_id
group by s.student_id
order by avg_grade desc nulls last
) as ce_avg
where avg_grade >= (
select ce_avg.avg_grade
from (
select s.student_id, s.student_name, avg(ce.grade) as avg_grade
from students as s
left join course_enrollment as ce on s.student_id = ce.student_id
group by s.student_id
order by avg_grade desc nulls last
) as ce_avg
limit 1 offset (select (count(*) * 0.1)::int from students) - 1
);
Tips:
Can't simply use ( limit %n * total
) or ( top n percent
) anyway. Since the students with the avg_grade = minimal top avg_grade, might be only partly included, Which is not fair.
The ugly sql above could handle that case, with performance cost.
Here is an example that shows the differences of the running results with duplication handled or unhandled:
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.