How to retrieve results for top items - sql

I have ran a query to give me the total number of students within each school but now I need to know the name of those students within each school while keeping the top result by total number at the top. How can I add to this query to show me the names of the students?
Here is what I have to show me the total number of students at each school:
SELECT
dbo_Schools.Schools,
Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID=dbo_tSchool.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC;
Its important that I keep the schools in order from top number of students while listing the students

In this case you could use a Sub Query to achieve your resultset.
To use order by inside a subquery, you will also need a top or limit operator.
SELECT sc.schoolname
,st.columns...
FROM dbo_tStudent st
INNER JOIN (
SELECT TOP 1000 dbo_Schools.SchoolID
,min(schoolname) schoolname
,Count(dbo_tStudent.Student) AS NumberOfStudents
FROM dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
) sc ON st.SchoolID = sc.SchoolID

Assuming that you are using SQL Server, you can use a CTE to join the first aggregate with the details like this:
;WITH cte as (
SELECT TOP 1000 dbo_Schools.SchoolID, Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
)
SELECT
sc.<your school name column>,
st.<your student columns>
from
dbo_tStudent st
INNER JOIN cte ON st.SchoolID = cte.SchoolID
INNER JOIN dbo_tSchools sc on cte.SchoolID = sc.SchoolID
More generally speaking: you need a derived table (your aggregation containing the group by clause) that is joined with the select statement for the student details. In this example, the CTE basically is a SQL Server feature that facilitates the use of derived tables.

Related

Join with count

I need to write SQL query like:
Show all countries with more than 1000 users, sorted by user count.
The country with the most users should be at the top.
I have tables:
● Table users (id, email, citizenship_country_id)
● Table countries (id, name, iso)
Users with columns: id, email, citizenship_country_id
Countries with columns: id, name, iso
SELECT countries.name,
Count(users.citiizenship_country_id) AS W1
FROM countries
LEFT JOIN users ON countries.id = users.citizenship_country_id
GROUP BY users.citiizenship_country_id, countries.name
HAVING ((([users].[citiizenship_country_id])>2));
But this does not work - I get an empty result set.
Could you please tell me what I'm doing wrong?
A LEFT JOIN is superfluous for this purpose. To have 1000 users, you need at least one match:
SELECT c.name, Count(*) AS W1
FROM countries c JOIN
users u
ON c.id = u.citizenship_country_id
GROUP BY c.name
HAVING COUNT(*) > 1000;
Notice that table aliases also make the query easier to write and to read.
Group by country name and use HAVING Count(u.citiizenship_country_id)>1000, it filters rows after aggregation:
SELECT c.name,
Count(u.citiizenship_country_id) AS W1
FROM countries c
INNER JOIN users u ON c.id = u.citizenship_country_id
GROUP BY c.name
HAVING Count(u.citiizenship_country_id)>1000
ORDER BY W1 desc --Order top counts first
;
As #GordonLinoff pointed, you can use INNER JOIN instead of LEFT JOIN, because anyway this query does not return counries without users and INNER JOIN performs better because no need to pass not joined records to the aggregation.

Slow MS Access Sub Query

I have three tables in Access:
employees
----------------------------------
id (pk),name
times
----------------------
id (pk),employee_id,event_time
time_notes
----------------------
id (pk),time_id,note
I want to get the record for each employee record from the times table with an event_time immediately prior to some time. Doing that is simple enough with this:
select employees.id, employees.name,
(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC) as time_id
from employees
However, I also want to get some indication of whether there's a matching record in the time_notes table:
select employees.id, employees.name,
(select top 1 time_notes.id from time_notes where time_notes.time_id=(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC)) as time_note_present,
(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC) as last_time_id
from employees
This does work but it's SOOOOO SLOW. We're talking 10 seconds or more if there's 100 records in the employee table. The problem is peculiar to Access as I can't use the last_time_id result of the other sub-query like I can in MySQL or SQL Server.
I am looking for tips on how to speed this up. Either a different query, indexes. Something.
Not sure if something like this would work for you?
SELECT
employees.id,
employees.name,
time_notes.id AS time_note_present,
times.id AS last_time_id
FROM
(
employees LEFT JOIN
(
times INNER JOIN
(
SELECT times.employee_id AS lt_employee_id, max(times.event_time) AS lt_event_time
FROM times
WHERE times.event_time <= #2018-01-30 14:21:48#
GROUP BY times.employee_id
)
AS last_times
ON times.event_time = last_times.lt_event_time AND times.employee_id = last_times.lt_employee_id
)
ON employees.id = times.employee_id
)
LEFT JOIN time_notes ON times.id = time_notes.time_id;
(Completely untested and may contain typos)
Basically, your query is running multiple correlated subqueries even a nested one in a WHERE clause. Correlated queries calculate a value separately for each row, corresponding to outer query.
Similar to #LeeMac, simply join all your tables to an aggregate query for the max event_time grouped by employee_id which will run once across all rows. Below times is the baseFROM table joined to the aggregate query, employees, and time_notes tables:
select e.id, e.name, t.event_time, n.note
from ((times t
inner join
(select sub.employee_id, max(sub.event_time) as max_event_time
from times sub
where sub.event_time <= #2018-01-30 14:21:48#
group by sub.employee_id
) as agg_qry
on t.employee_id = agg_qry.employee_id and t.event_time = agg_qry.max_event_time)
inner join employees e
on e.id = t.employee_id)
left join time_notes n
on n.time_id = t.id

Combining FREQUENCY count and INNER CASE in SQL

How do I combine both in order to show the student name (sname) and section number (sectno) for a class that has more than 6 student? So far I have this
SELECT student.sname, enroll.sectno,
FROM student
INNER JOIN enroll
ON student.sid=enroll.sid
with
SELECT grade,
COUNT(grade)AS Frequency
FROM enroll
GROUP BY grade
HAVING COUNT(grade)>6
looks like you were very close. I think the below should work for you:
SELECT
student.sname,
enroll.sectno,
COUNT(enroll.grade) AS Frequency
FROM student
INNER JOIN enroll ON student.sid=enroll.sid
GROUP BY student.sname, enroll.sectno
HAVING COUNT(enroll.grade)>6
In your 2md Select there's no relation to a class that has more than 6 student, IMHO the GROUP BY should be based on column like classid. And then you can simply combine both using a Windowed Aggregate:
with cte as
(
SELECT student.sname, enroll.sectno,
-- get the count per class, might be a different column than sectno
count(*) over (partition by enroll.sectno) as cnt
FROM student
INNER JOIN enroll
ON student.sid=enroll.sid
)
select * from cte
where cnt > 6

How to get the value of max() group when in subquery?

So i woud like to find the department name or department id(dpmid) for the group that has the max average of age among the other group and this is my query:
select
MAX(avg_age) as 'Max average age' FROM (
SELECT
AVG(userage) AS avg_age FROM user_data GROUP BY
(select dpmid from department_branch where
(select dpmbid from user_department_branch where
user_data.userid = user_department_branch.userid)=department_branch.dpmbid)
) AS query1
this code show only the max value of average age and when i try to show the name of the group it will show the wrong group name.
So, How to show the name of max group that has subquery from another table???
You may try this..
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##' ,department_name)) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I've done some change on ipothesys that the department name is placed in a "department" anagraphical table.. so, as it needed put in join a table in plus, then I changed your query, eventually if the department name is placed (but I don't thing so) in the branch_department table you can add the field and its treatment to your query
update
In adjunct to as said, if you wanto to avoid identical average cases you can furtherly make univocal the averages by appending a rownum id in this way:
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##', CONCAT( #rownum:=#rownum+1, CONCAT('##' ,department_name)))) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
,(SELECT #rownum:=0) r
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I took a shot at what I think you are looking for. The following will give you the department branch with the highest average age. I assumed the department_branch table had a department_name field. You may need an additional join to get the department.
SELECT db.department_name, udb.dpmid, AVG(userage) as `Average age`
FROM user_data as ud
JOIN user_department_branch as udb
ON udb.userid = ud.userid
JOIN department_branch as db
ON db.dpmbid = udb.dpmbid
GROUP BY udb.dpmid
ORDER BY `Average age` DESC
LIMIT 1

Returning the Min() of a Count()

I am studying for an SQL test and the previous year has the final question:
Name the student who has studied the least number of papers. How many
papers have they studied?
So far, this is the select query that I have created:
select min(Full_Name), min(Amount)
from (select st.ST_F_Name & ' ' & st.ST_L_Name as Full_Name, count(*) as Amount
from (student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID)
group by st.ST_F_Name & ' ' & st.ST_L_Name)
This works perfectly for returning the result I want but I'm not sure if this is the way I should be doing this query? I feel like calling min() on the Full_Name could potentially backfire on me under certain circumstances. Is there a better way to be doing this? (this is in MS Access for unknown reasons)
If you want only 1 of such students if there are multiple, this is probably the simplest:
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
order by Amount ASC LIMIT 1
However, if you want to find all stuch students, you follow a different approach. We use a WITH clause to simplify things, that defines a CTE (Common Table Expression) computing the number of courses per-student. And then we select students where their number equals to the minimum in that CTE:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
But the real trick in that question is that there might be students that didn't take ANY courses. But with approaches presented so far you'll never see them. You want something like this:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(sc.SC_ST_ID) as Amount
from student_course as sc
right outer join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
You can order by count(*) to get the student with the least # of papers:
i.e.
select * from students where st_id in (
select top 1 sc_st_id
from student_course
group by sc_st_id
order by count(*)
)
if you also need the # of papers studied, then join a derived table containing the min count:
select * from students s
left join (
select top 1 sc_st_id, count(*)
from student_course
group by sc_st_id
order by count(*)
) t on t.sc_st_id = s.st_id