How to add this condition to this query? - sql

I'm working on this query:
SELECT s.studentname,
Avg(cs.exam_season_one
+ cs.exam_season_two
+ cs.degree_season_one
+ cs.degree_season_two ) / 4 AS average
FROM courses_student cs
join students s
ON s.student_id = cs.student_id
join SECTION se
ON s.sectionid = se.sectionid
WHERE cs.courses_id = 1
AND ( se.classes_id = 2
OR se.classes_id = 5 )
AND s.studentname LIKE 'm%'
GROUP BY s.studentname
until this moment everything works perfectly but I need to add a last condition and I dont know how.
I need to get the sudents with the same average
I mean only students with count(average) > 1 (idk if this is right)
anyone knows how to solve this problem in this query?
PS: I use oracle.
Edit:
The create tables statements and sample data are here:
http://sqlfiddle.com/#!4/ebf636
trying to explain the problem more because maybe I did it the wrong way the first time!
First, the average is the average of 4 columns
The output I expect is to get the names of the students who belong to class 2 or class 5 (classes_id = 2, classes_id = 5), also their name should start with M
I want to check their average in a specific course (course_id = 1)
and the last condition I'm asking about is that I want to get the students who only have the same average in this course.
for example:
if we have 4 students and the averages in the course are (60,70,80,80) then I want to get only the last 2 student names because they have the same average. hope it's clear now!

You appear to be asking for the students where the average of the averages of their exam and degree seasons 1 and 2 are greater than 1 for certain sections.
Without sample data and expected output to validate against, its difficult to answer but, for each student, you want to GROUP BY the primary key that uniquely identifies the student (otherwise you may aggregate two students with the same name together) and you only need to check if the section exists (rather than JOINing the section as that would create duplicate rows if there are multiple matching sections and skew the averages).
Then
if we have 4 students and the averages in the course are (60,70,80,80) then I want to get only the last 2 student names because they have the same average
You want to count how many students have the same average and then filter out those with unique averages:
SELECT studentname,
average
FROM (
SELECT a.*,
COUNT(*) OVER (PARTITION BY average) AS num_with_average
FROM (
SELECT MAX(s.studentname) AS studentname,
( Avg(cs.exam_season_one)
+ Avg(cs.exam_season_two)
+ Avg(cs.degree_season_one)
+ Avg(cs.degree_season_two) ) / 4 AS average
FROM courses_student cs
join students s
ON s.student_id = cs.student_id
WHERE cs.courses_id = 1
AND s.studentname LIKE 'm%'
AND EXISTS(
SELECT 1
FROM section se
WHERE s.sectionid = se.sectionid
AND se.classes_id IN (2, 5)
)
GROUP BY
s.student_id
) a
)
WHERE num_with_average > 1;
db<>fiddle here

Related

find an average of a column using group with inner join and then filtering through the groups

I've been trying to solve an sqlite question where I have two tables: Movies and movie_cast.
Movies has the columns: id, movie_title, and `score. Here is a sample of the data:
11|Star Wars|76.496
62|2001:Space Odyssey|39.064
152|Start Trek|26.551
movie_cast has the columns: movie_id, cast_id, cast_name, birthday, popularity. Here is a sample.
11|2|Mark Hamill|9/25/51|15.015
11|3|Harrison Ford|10/21/56|8.905
11|5|Peter Cushing|05/26/13|6.35
IN this case movies.id and movie_cast.movie_id are the same.
The question is to Find the top ten cast members who have the highest average movie scores.
Do not include movies with score <25 in the average score calculation.
▪ Exclude cast members who have appeared in two or fewer movies.
My query is as below but it doesn't seem to get me the right answer.
SELECT movie_cast.cast_id,
movie_cast.cast_name,
printf("%.2f",CAST(AVG(movies.score) as float)),
COUNT(movie_cast.cast_name)
FROM movies
INNER JOIN movie_cast ON movies.id = movie_cast.movie_id
WHERE movies.score >= 25
GROUP BY movie_cast.cast_id
HAVING COUNT(movie_cast.cast_name) > 2
ORDER BY AVG(movies.score ) DESC, movie_cast.cast_name ASC
LIMIT 10
The answers I get are in the format cast_id,cat_name,avg score.
-And example is: 3 Harrison Ford 52.30
I've analyzed and re-analyzed my logic but to no avail. I'm not sure where I'm going wrong. Any help would be great!
Thank you!
This is how I would write the query:
SELECT mc.cast_id,
mc.cast_name,
PRINTF('%.2f', AVG(m.score)) avg_score
FROM movie_cast mc INNER JOIN movies m
ON m.id = mc.movie_id
WHERE m.score >= 25
GROUP BY mc.cast_id, mc.cast_name
HAVING COUNT(*) > 2
ORDER BY AVG(m.score) DESC, mc.cast_name ASC
LIMIT 10;
I use aliases for the tables to shorten the code and make it more readable.
There is no need to cast the average to a float because the average in SQLite is always a real number.
Both COUNT(movie_cast.cast_name) can be simplified to COUNT(*) but the 1st one in the SELECT list is not needed by your requirement (if it is then add it).
The function PRINTF() returns a string, but if you want a number returned then use ROUND():
ROUND(AVG(m.score), 2) avg_score

How to get MAX value out of the GROUPs COUNT

I've recently started to learn tsql beyond basic inserts and selects, I have test database that I train on, and there is one query that I can't really get to work.
There are 3 tables used in that query, in the picture there are simplified fields and relations
I have 2 following queries - first one is simply displaying students and number of marks from each subject. Second is doing almost what I want to achive - shows students and maxiumum amount of marks they got, so ex.
subject1 - (marks) 1, 5, 3, 4 count - 4
subject2 - (marks) 5, 4, 5 - count - 3
Query shows 4 and from what I checked it returns correct results, but I want one more thing - just to show the name of the subject from which there is maximum amount of marks so in the example case - subject1
--Query 1--
SELECT s.Surname, subj.SubjectName, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE m.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName
ORDER BY s.Surname
--Query 2--
SELECT query.Surname, MAX(Marks_count) as Maximum_marks_count FROM (SELECT s.Surname, subj.SubjectNumber, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE marks.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName) as query
GROUP BY query.Surname
ORDER BY query.Surname
--Query 3 - not working as supposed--
SELECT query.Surname, query.SubjectName, MAX(Marks_count) as Maximum_marks_count FROM (SELECT s.Surname, subj.SubjectNumber, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE marks.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName) as query
GROUP BY query.Surname, query.SubjectName
ORDER BY query.Surname
Part of the query 1 result
Part of the query 2 and unfortunately query 3 result
The problem is that when I add to the select statement subject name I got results as from query one - there is no more maximum amount of marks just students, subjects and amount of marks from each subject.
If someone could say what I'm missing, I will much appreciate :)
Here's a query that gets the highest mark per student, put it at the top of your sql file/batch and it will make another "table" you can join to your other tables to get the student name and the subject name:
WITH studentBest as
SELECT * FROM(
SELECT *, ROW_NUMBER() OVER(PARTITION BY studentid ORDER BY mark DESC) rown
FROM marks) a
WHERE rown = 1)
You use it like this (for example)
--the WITH bit goes above this line
SELECT *
FROM
studentBest sb
INNER JOIN
subject s
ON sb.subjectid = s.subjectnumber
Etc
That's also how you should be doing your joins
How does it work? Well.. it establishes an incrementing counter that restarts every time studentid changes (the partition clause) and the numberin goes in des ending mark order (the order by clause). An outer query selects only those rows with 1 in the row number, ie the top mark per student
Why can't I use group by?
You can, but you have to write a query that summarises the marks table into the top mark (max) per student and then you have to join that data back to the mark table to retrieve the subject and all in it's a lot more faff, often less efficient
What if there are two subjects with the same mark?
Use RANK instead of ROW_NUMBER if you want to see both
Edit in response to your comment:
An extension of the above method:
SELECT * FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY su, st ORDER BY c DESC) rn FROM
(
SELECT studentid st, subjectid su, count(*) c
FROM marks
GROUP BY st, su
) a
) b
INNER JOIN student stu on b.st = stu.studentnumber
INNER JOIN subject sub on b.su = sub.subjectnumber
WHERE
b.rn = 1
We count the marks by student/subject, then rownumber them in descending order of count per student-subject pair, then choose only the first row and join in the other wanted data
Ok thanks to Caius Jard, some other Stack's question and a little bit of experiments I managed to write working query, so this is how I did it.
First I created view from query1 and added one more column to it - studentId.
Then I wrote query which almost satisfied me. That question helped me a lot with that task: Question
SELECT marks.Surname,
marks.SubjectName,
marks.Marks_count,
ROW_NUMBER() OVER(PARTITION BY marks.Surname ORDER BY marks.Surname) as RowNum
FROM MarksAmountPerStudentAndSubject marks
INNER JOIN (SELECT MarksAmountPerStudentAndSubject.Id,
MAX(MarksAmountPerStudentAndSubject.Marks_count) as MaxAmount
FROM MarksAmountPerStudentAndSubject
GROUP BY MarksAmountPerStudentAndSubject.Id) m
ON m.Id = marks.Id and marks.Marks_count = m.MaxAmount
It gives following results
That's what I wanted to achieve with one exception - if students have the same amount of marks from multiple subjects it displays all of them - thats fine but I decided to restrict this to the first result for each student - I couldn't just simply put TOP(1)
there so I used similar solution that Caius Jard showed - ROW_NUMBER and window function - it gave me a chance to choose records that has row number equals to 1.
I created another view from this query and I could simply write the final one
SELECT marks.Surname, marks.SubjectName, marks.Marks_count
FROM StudentsMaxMarksAmount marks
WHERE marks.RowNum = 1
ORDER BY marks.Surname
With result

SQL query to find The attendance % of the group leader should be greater than the avg. attendance % of all members of HIS group

In a class of 100 students, the Class Teacher wants to create groups of 10 with one group leader each. Students are allotted roll numbers (1,2,3…,100) based on the alphabetic ordering of their names. The CT goes with a simple approach and creates 10 groups basis their roll numbers. He also assigns one random person from the group to be the group leader -
G1 - 1 to 10 [Group leader - 6]
G2 - 11 to 20 [Group leader - 14]
G3 - 21 to 30 [Group leader - 25]
and so on…
Write a SQL query to classify the group leader elected by the CT as ‘eligible’/’not_eligible’ basis the eligibility criteria -
Eligibility criteria : The attendance % of the group leader should be greater than the avg. attendance % of all members of HIS group.
The data is stored in 2 tables as following -
Output required:
"The CT goes with a simple approach and creates 10 groups basis their roll numbers."
That's where the problem is. He should take the 10 persons with the highest attendance % and make those the group leaders. Then your difficult problem of assessing "eligibility" has evaporated by definition.
But no doubt this is homework so here is the strategy :
1) Using joins, get a four-column table (roll_number student_attendance leader_roll_number leader_attendance)
2) Compute the AVG(student_attendance) per group (GROUP BY leader_roll_number)
3) Compare that to leader_attendance and using a CASE expression compute the value for your "eligible" column
I'm not going to spoonfeed you the SQL itself.
If you need the query, here it is:
select distinct aa.leader_roll_number, (min_roll_no||'-'||max_roll_no) as grp , bb.student_attendance as leader_attendance,
case when avg(aa.student_attendance) over (partition by aa.leader_roll_number) < bb.student_attendance then 'eligible' else 'not eligibe' end as eligibility from
(
select roll_number, student_attendance, leader_roll_number, min(roll_number) over (partition by leader_roll_number) as min_roll_no, max(roll_number) over (partition by leader_roll_number) as max_roll_no from
(
select a.*, b.leader_roll_number from student_attendance a
join group_leader_mapping b on a.roll_number=b.roll_number
)
) aa
join
(
select distinct leader_roll_number, student_attendance from group_leader_mapping a
join student_attendance b on a.leader_roll_number=b.roll_number
) bb on aa.leader_roll_number=bb. leader_roll_number
order by 1

SQL,Nested Queries (MS ACCESS)

I am trying to tackle a problem but seem to be getting nowhere. I want to display Grade 12 students who scored below average for Maths then instead of displaying their average display their maths marks instead.
I am using msAccess and suspect the use of nested queries are necessary.The fields I am working with are first_name, last_name, grade (from 1 to 12) and Maths (containing maths marks)
I have this:
Select first_name,last_name,maths
FROM students
WHERE grade = 12
HAVING ROUND(AVG(maths),1)< maths;
Output:
Error:
You tried to execute a query that does not include the specified expression 'first_name' as part of an aggregate function
However, I do not know why it is throwing this error and it repeats like this even after removing the field from select which I don't want to do in the first place because I need to display it
To get the users who scored below the average, you can do a query similar to yours, but with a group by:
select s.student_id, avg(maths) as avg_maths
from students as s
where s.grade = 12
group by s.student_id
having avg(maths) < (select avg(maths) from students where grade = 12);
(Note: This assumes that you have an id for each student, rather than using the name.)
Next, you can get the original maths scores in various ways. One simple way uses in:
select first_name, last_name, maths
from students
where grade = 12 and
student_id in (select s.student_id, avg(s.maths) as avg_maths
from students as s
where s.grade = 12
group by s.student_id
having avg(maths) < (select avg(maths) from students where grade = 12)
);

SQL add multiple "Count" together

I'm trying to add the counts together and output the one with the max counts.
The question is: Display the person with the most medals (gold as place = 1, silver as place = 2, bronze as place = 3)
Add all the medals together and display the person with the most medals
Below is the code I have thought about (obviously doesn't work)
Any ideas?
Select cm.Givenname, cm.Familyname, count(*)
FROM Competitors cm JOIN Results re ON cm.competitornum = re.competitornum
WHERE re.place between '1' and '3'
group by cm.Givenname, cm.Familyname
having max (count(re.place = 1) + count(re.place = 2) + count(re.place = 3))
Sorry forgot to add that were not allowed to use ORDER BY.
Some data in the table
Competitors Table
Competitornum GivenName Familyname gender Dateofbirth Countrycode
219153 Imri Daniel Male 1988-02-02 Aus
Results Table
Eventid Competitornum Place Lane Elapsedtime
SWM111 219153 1 2 20 02
From what you've described it sounds like you just need to take the "Top" individual in the total medal count. In order to do that you would write something like this.
Select top 1 cm.Givenname, cm.Familyname, count(*)
FROM Competitors cm JOIN Results re ON cm.competitornum = re.competitornum
WHERE re.place between '1' and '3'
group by cm.Givenname, cm.Familyname
order by count(*) desc
Without using order by you have a couple of other options though I'm glossing over whatever syntax peculiarities sqlfire may use.
You could determine the max medal count of any user and then only select competitors that have that count. You could do this by saving it out to a variable or using a subquery.
Select cm.Givenname, cm.Familyname, count(*)
FROM Competitors cm JOIN Results re ON cm.competitornum = re.competitornum
WHERE re.place between '1' and '3'
group by cm.Givenname, cm.Familyname
having count(*) = (
Select max( count(*) )
FROM Competitors cm JOIN Results re ON cm.competitornum = re.competitornum
WHERE re.place between '1' and '3'
group by cm.Givenname, cm.Familyname
)
Just a note here. This second method is highly inefficient because we recalculate the max medal count for every row in the parent table. If sqlfire supports it you would be much better served by calculating this ahead of time, storing it in a variable and using that in the HAVING clause.
You are grouping by re.place, is that what you want? You want the results per ... ? :)
[edit] Good, now that's fixed you're almost there :)
The having is not needed in this case, you simply need to add a count(re.EventID) to your select and make a subquery out of it with a max(that_count_column).