Tricky statistics select - sql

Trying to figure out this for a while, but no luck. I have the following tables (MS-SQL 2008):
students
studentID – email – profileID
courses
courseID – name
studentsCourses
studentID – courseID
profiles
profileID – name
profilesMandatoryCourses
profileID - courseID
studentsCoursesLogs
logID - studentID –courseID – accessDate
Each student, when enrolls, is assigned a profile. For each profile there are a number of mandatory course. Those mandatory courses together with any other courses a user takes are saved in the studentsCourses table.
Whenever a student accesses a course the information is logged in the studentsCoursesLogs.
I am trying to figure out all the students that have taken all the mandatory courses based on their profile.
Any pointers appreciated.

I think this should do it (assuming I've understood your data model and requirements correctly - and that in throwing this sample SQL together I've not made any mistakes - I didn't create your data model in a database). I'm pretty sure it should be close enough though.
select studentRecords.StudentId,
sum(case TakenCourseID when null then 0 else 1 end) as CompleteMandatoryCourses,
sum(case TakenCourseID when null then 1 else 0 end) as IncompleteMandatoryCourses
from (
select mandatoryCourses.StudentID, mandatoryCourses.CourseId as MandatoryCourseID, takenCourses.CourseID as TakenCourseID
from ( -- Courses student should have taken - based on their profile
select p.[Profile], pmc.CourseID, s.StudentID
from profiles p
inner join profilesMandatoryCourses pmc on p.ProfileID = pmc.Profile
inner join students s on p.StudentID = s.StudentID
) mandatoryCourses
left join
(
-- Course students have taken
select s.StudentID, s.ProfileID, sc.CourseID
from students s
inner join studentsCourseLogs scl on s.StudentID = scl.StudentID
) takenCourses on mandatoryCourses.ProfileId = takenCourses.ProfileID
and mandatoryCourses.CourseID = takenCourses.CourseID
) studentRecords
group by mandatoryCourse.StudentId
having sum(case TakenCourseID when null then 1 else 0 end) = 0
It will give a recordset like the following....
+----------------+-----------------------------+-------------------------------+
| StudentID | CompleteMandatoryCourses | IncompleteMandatoryCourses |
+----------------+-----------------------------+-------------------------------+
| 1 | 15 | 3 |
| 2 | 8 | 0 |
+----------------+-----------------------------+-------------------------------+
If you just want a list of students who have taken all mandatory courses, you could wrap all of the above as shown here...
select studentID
from ( /* insert very long sql here */ )
where IncompleteMandatoryCourses = 0`

Related

SQL : get one particular book owner

I have a Student table with columns like this:
| email (PK) | name |
I have a book table with columns as such:
| bookid(PK) | title |
I have a copy table which have copies of books people own
| emailofOwner(FK to student.email) | bookid(FK to book.bookid) |
A student can of course own multiple books. My aim is to find names of students who own only 1 such book and nothing else BUT with a bookid = 3;
My attempt to get people who own only 1 book.
select c.emailofOwner
from copy c
group by c.emailofOwner
having count(*) = 1 ;
SELECT t1.name
FROM student t1
INNER JOIN
(
SELECT emailofOwner
FROM copy
GROUP BY emailofOwner
HAVING COUNT(DISTINCT bookid) = 1 AND MAX(bookid) = 3
) t2
ON t1.email = t2.emailofOwner
The above query uses a subquery to restrict to students who own one and only one book whose ID is 3. The subquery is identical to your attempt except that it adds the restriction that the max book ID is 3. In this case, since there will only be one book per retained group, this is simply checking the value of the book ID.
To get students with only
select s.name, s.email, count(*) as numBooks
from student s, copy c
where s.email = c.emailOfOwner
group by email
having count(*) = 1
And people with book 3 and ONLY book 3:
select s.name, s.email, count(*) as numBooks
from student s, copy c
where s.email = c.emailOfOwner
group by email
having count(*) = 1 and min(bookId) = 3;
Check out this SQL Fiddle.

SQL query using NULL

I have two tables, student and school.
student
stid | stname | schid | status
school
schid | schname
Status can be many things for temporary students, but NULL for permanent students.
How do I list names of schools which has no temporary students?
Using Conditional Aggregate you can count the number of permanent student in each school.
If total count of a school is same as the conditional count of a school then the school does not have any temporary students.
Using JOIN
SELECT sc.schid,
sc.schname
FROM student s
JOIN school sc
ON s.schid = sc.schid
GROUP BY sc.schid,
sc.schname
HAVING( CASE WHEN status IS NULL THEN 1 END ) = Count(*)
Another way using EXISTS
SELECT sc.schid,
sc.schname
FROM school sc
WHERE EXISTS (SELECT 1
FROM student s
WHERE s.schid = sc.schid
HAVING( CASE WHEN status IS NULL THEN 1 END ) = Count(*))
You can use not exists to only select schools that do not have temporary students:
select * from school s
where not exists (
select 1 from student s2
where s2.schid = s.schid
and s2.status is not null
)
You can use a regular join.
SELECT DISTINCT c.schName
FROM Students s
INNER JOIN Schools c ON s.schid = c.schid
WHERE s.status IS NULL

SQL: tricky join

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 attends an education
Educations have multiple exams
Students have up to 6 attempts per exam
Every attempt is graded or marked as not present
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...
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.
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))
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

Sql Query Join in Oracle

I have Parent table and multiple child tables with foreign key constraint.
School Table
School ID EduDetails Genders Address_id EDUTYPE
1 2 M 3 FGN
And the child tables like
Education Details
EDU ID EducationType
2 Online
AKA Name
School Id AKA Name
1 Test School
1 School Test
Gender Table
Gender ID Gender Desc
M Male
I am using Left outer join for the parent and school table to fetch the results.
But My issue is, If AKA table has 5 counts matching the school Id and Gender table has only 1 records for that school Id.
So the results comes with 5 duplicate rows with school Information and also other child table information.
Is there any workaround to fix this issue. I tried using subquery and row_number over by function. But it is not working for me. Can anybody help me to solve this issue.
Thanks in advance for your time in looking this issue.
My required output should be like this
School_id AKA Name GenderDesc EductaionType
1 Test School Male Online
1 School Test
So I need to have Null values for the not matching records.
Since you want all the records in the AKA Name table, I've joined on that getting a Row_Number for each row. Then using that Row_Number, LEFT JOIN on the other tables.
SELECT S.SchoolId,
SA.AKAName,
G.GenderName,
ED.EducationType
FROM School s
JOIN
(SELECT SchoolId,
AKAName,
ROW_NUMBER() OVER (PARTITION BY SchoolId ORDER BY AKAName) rn
FROM SchoolAKA
) SA ON S.SchoolID = SA.SchoolId
LEFT JOIN
(SELECT EDUID,
EducationType,
ROW_NUMBER() OVER (ORDER BY EducationType) rn
FROM EduDetails
) ED ON S.EDUID = ED.EDUID AND SA.rn = ED.rn
LEFT JOIN
(SELECT GenderId,
GenderName,
ROW_NUMBER() OVER (ORDER BY GenderName) rn
FROM Genders
) G ON S.GenderId = G.GenderId AND SA.rn = G.rn
Here is the SQL Fiddle.
And here are the results:
SCHOOLID AKANAME GENDERNAME EDUCATIONTYPE
1 School Test Male Online
1 Test School (null) (null)

find missing values in MySQL

I am trying to write a query to find out what users are not enrolled on some courses.
I have 2 tables; courses, and users. Both contain the fields 'id' and then 'coursename' and username' respectively.
Using these two table the existing system adds users to a third table (called enrolled_users). Users take up one row for each. For example if user id '1' is on 4 courses the enrolled_users looks like this:
-----------------------
| user_id | course_id |
-----------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
-----------------------
As you can see this table data is not built on a relationship, it is generated in PHP using the two tables 'courses' and 'users'.
I am not sure how to write a query on how to find out that courses user '1' is not enrolled in, if there are say 10 courses, but we can see user '1' is only on courses 1-4.
Am I suppose to use something like a LIKE or NOT LIKE?
SELECT u.id, c.id, ec.user_id, ec.course_id
FROM users u, courses c, enrolled_courses ec
WHERE u.id = ec.user_id
AND c.id = ec.course_id
AND u.id = '1'
I have tried using != and <> but this doesn't show what i need; a list of courses the user is not enrolled on.
Sorry if I am being vague, trying to get my head round it!
Using MySQL 5.0, on an existing system I cannot modify, only query from (for the moment).
SELECT
*
FROM
courses c
WHERE
c.id NOT IN (
SELECT
ec.course_id
FROM
enrolled_courses ec
WHERE
ec.user_id = 1 AND ec.course_id IS NOT NULL
)
If you are looking for a specific single user ID, be sure to include that ID condition as part of the WHERE clause, otherwise, leave it blank, and it will give you ALL people who are not registered for ALL possible classes via a Cartesian product (since no direct join between user and courses table) .. this could be VERY large / time consuming if trying to run for everyone and your course table is 100s of courses where someone would only be involved in 2-3-4 courses.
select
u.id,
c.id CourseID
from
users u,
courses c
where
u.id = 1
AND c.id NOT IN
( select ec.Course_ID
from enrolled_courses ec
where ec.user_id = u.id )
Just use a not in and a sub select.
select * from courses c where c.id not in
(select course_id from enrolled_courses where user_id='1')