Reputation: 6810
I'm building software for exam signup and grading:
I need to get data from these two tables:
Exams
|------------------------------------------------------------|
| ExamId | ExamTitle | EducationId | ExamDate |
|------------------------------------------------------------|
ExamAttempts
|-----------------------------------------------------------------------------|
| ExamAttemptId | ExamId | StudentId | Grade | NotPresentCode |
|-----------------------------------------------------------------------------|
Students can sign up for an exam if: - not passed yet (grade below 2) - has not used all attempts
I want to list every exams that a student can sign up for.
It maybe fairly simple, but I just can't get my head around it and now I'm stuck! I've tried EVERYTHING but haven't got it right yet. This is one of the more hopeless tries I made (!):
CREATE PROCEDURE getExamsOpenForSignUp
@EducationId int,
@StudentId int
AS
SELECT ex.*
FROM Exams ex
LEFT JOIN (
SELECT ExamId, COUNT(ExamId) AS NumAttempts
FROM ExamAttempts
WHERE StudentId = @StudentId AND grade < 2 OR grade IS NULL
GROUP BY ExamId
) exGrouped ON ex.ExamId = exGrouped.ExamId
WHERE educationid = @EducationId and exGrouped.ExamId IS NULL OR exGrouped.NumAttempts < 6;
GO
What am i doing wrong? Please help...
Upvotes: 2
Views: 651
Reputation: 7612
Check if this works in your SP:
Select EduExams.ExamId
from
(select * from Exams
where Exams.EducationId = @EducationId) EduExams
left outer join
(select * from ExamAttempts
where ExamAttempts.StudentId = @StudentId) StudentAttempts
on EduExams.ExamID = StudentAttempts.ExamId
group by EduExams.ExamId
having count(StudentAttempts.ExamAttemptId) < 6
and ((max(StudentAttempts.Grade) is null) or (max(StudentAttempts.Grade) < 2))
Upvotes: 1
Reputation: 6810
OK thank you both for your help - much appreciated!
Based on @Gordon Linoffs solution. this is what I ended up with:
SELECT driver.ExamId, driver.ExamTitle
FROM (
SELECT @StudentId AS StudentId, e.ExamId, e.ExamTitle
FROM exams e
WHERE e.EducationId = @EducationId
) driver
LEFT JOIN (
SELECT ea.ExamId, ea.StudentId
FROM ExamAttempts ea
WHERE ea.studentId = @StudentId
GROUP BY ea.ExamId, ea.StudentId
HAVING MAX(grade) >= 2 OR COUNT(*) >= 6
) NotEligible
ON driver.ExamId = NotEligible.ExamId AND driver.StudentId = NotEligible.StudentId
WHERE NotEligible.ExamId IS NULL
Upvotes: 0
Reputation: 1269933
You need to start with a list of all possibilities of exams and students and then weed out the ones that don't meet the requirements.
select driver.StudentId, driver.ExamId
from (select @StudentId as StudentId, e.ExamId
from exams e
where e.EducationId = @EducationId
) driver left outer join
(select ea.ExamId, ea.StudentId
from ExamAttempts ea
group by ea.ExamId, ea.StudentId
having max(grade) >= 2 or -- passed
count(*) >= 6
) NotEligible
on driver.ExamId = NotEligible.ExamId and
driver.StudentId = NotEligible.StudentId
where NotEligible.ExamId is NULL
The structure of this query is quite specific. The driver
table contains all possible combinations. In this case, you have only one student and all exams are in the "education". Then the left join determines which are not eligible, based on your two requirements. The final where
is selecting the non-matches to the not-eligible -- or the exams that are eligible.
Upvotes: 1