Need SQL query to export students attendance report by classes - sql

I have 3 tables:
students table
id
name
1
Jhon
2
Emma
3
Oliver
classes table
id
name
1
Math
2
Science
attendance table
id
student_id
class_id
1
1
1
2
1
2
3
2
1
I tried to write an SQL query to retrieve the students who attended both math and science classes:
SELECT s.id, s.name
FROM attendance a
INNER JOIN students s ON a.student_id = s.id
WHERE a.class_id IN (1,2);
the above code result is
id
name
1
Jhon
2
Emma
1
Jhon
But Emma only attended Math class.
I know this behavior is because of WHERE IN, it's the same as WHERE a.class_id = 1 OR a.class_id = 2.
But what should I do to get the expected results, Which is "Jhon" or only Jhon's ID because he is the only one who attended Math and Science Classes?

Either join to attendance twice, once for each class:
SELECT s.id, s.name
FROM students s
JOIN attendance a1 ON a1.student_id = s.id and a1.class_id = 1
JOIN attendance a2 ON a2.student_id = s.id and a2.class_id = 2
or join to both classes at once and use group by with having:
SELECT s.id, s.name
FROM students s
JOIN attendance a ON a.student_id = s.id and class_id in (1, 2)
GROUP BY s.id, s.name
HAVING COUNT(*) = 2

Using a CTE would help, and can help you extend to multiple classes. You are also missing a DISTINCT keyword, which is why Jhon appears twice. Maybe something like this:
WITH attendance_count(student_id, classes_attended)
AS (
SELECT
student_id,
COUNT(id) AS classes_attended
FROM attendance a
-- change this to change the classes
WHERE a.class_id IN (1,2)
GROUP BY student_id
)
SELECT DISTINCT s.id, s.name
FROM attendance_count
INNER JOIN students s ON attendance_count.student_id = s.id
-- if different number of classes, change this
WHERE classes_attended = 2

Related

SQL - JOIN 3 tables but show where data is missing from the middle table

Given some Students:
StudentID Name
---------------
1 James
2 Emma
Given some Exams:
ExamID Name
---------------
1 Maths
2 English
3 Science
Each student will eventually have a Mark for each exam, but we're midway through exams and some marks won't exist yet:
MarkId StudentId ExamId Grade
-----------------------------
1 1 1 B
2 1 2 A
3 2 1 A
How can I query that (middle) Mark table to show all existing marks, but a null row where a student hasn't taken an exam yet?
The results I'm after should look like this:
I thought I should be able to get this with a RIGHT JOIN, but the 3 JOINs always throw the NULLs away.
Use a cross join to generate the rows and left join to bring in the grades:
select s.*, e.*, m.grade
from students s cross join
exams e left join
marks m
on m.studentid = s.studentid and m.examid = e.examid;

SQL: Retrieve all records where has all joined

Sorry, couldn't think of a better title.
I have 3 tables in Oracle XE. An EMPLOYEE table a PROJECT table and a WORK_ON table. An EMPLOYEE can WORK_ON many PROJECTs. I am trying to get the employee name who is working on all the projects.
EMPLOYEE Table
Emp_ID EMP_Name
1 Esther
2 Peter
3 Joan
4 Roger
5 Liam
PROJECT Table
Project_ID
1
2
3
WROKS_ON Table
Emp_ID Project_ID
1 3
2 1
2 2
2 3
3 1
3 2
4 1
4 2
4 3
Given the fields my result should be Peter and Roger.
Started with the following, but got stuck:
SELECT EMP_NameLOYEE.E_NAME
FROM EMP_NameLOYEE INNER JOIN
(PROJECT INNER JOIN WROKS_ON ON PROJECT.Project_ID = WROKS_ON.Project_ID) ON
EMP_NameLOYEE.Emp_ID = WROKS_ON.Emp_ID
WHERE WROKS_ON.Project_ID In (SELECT DISTINCT Project_ID FROM PROJECT);
Obviously this retrieves all the names of the employees that are working on each project duplicated, but not exactly what I want.
You can leave the project table out of it.
SELECT e.emp_id, COUNT(project_id)
FROM employee e
INNER JOIN works_on wo ON wo.emp_id = e.emp_id
GROUP BY e.emp_id
HAVING COUNT( project_id ) = (SELECT COUNT(*) FROM project);
SQL Fiddle
You need to generate all combinations of employees and projects with a cross join and left join the works table and check for row counts for each e_name.
SELECT e.E_NAME
FROM EMPLOYEE e
CROSS JOIN PROJECT p
LEFT JOIN WORKS_ON w ON p.Project_ID = w.Project_ID and e.emp_id=w.emp_id
GROUP by e.E_NAME
HAVING COUNT(*)=COUNT(w.project_id)

Query that gets students with their courses in same row

I need a query that will get students whom get Course1, Course2 and Course3. I can query it like that:
SELECT k.name as firstname, k.surname as lastname, k.Email
FROM Students k
JOIN StudentCourses dn ON dn.StudentID = k.StudentID
WHERE dn.CourseID IN
(SELECT CourseID FROM Courses WHERE CourseName IN ('Course1','Course2','Course3'))
But i need a result set like that: Name, Surname, Email, Course1, Course2, Course3. No multiple rows for a student, one row and write course name if students get that course.
In fact i can imagine how to write that query (with subselects), wonder about better alternatives.
You can use the PIVOT table operator to do this, something like this:
WITH CTE
AS
(
SELECT
k.name as firstname,
k.surname as lastname,
k.Email,
c.CourseName, c.CourseID
FROM Students k
JOIN StudentCourses dn ON dn.StudentID = k.StudentID
INNER JOIN Courses c ON dn.CourseID = c.CourseID
WHERE c.CourseName IN ('Course1','Course2','Course3')
)
SELECT *
FROM CTE AS c
PIVOT
(
MAX(CourseID)
FOR CourseName IN ([Course1], [Course2], [Course3])
) u;
SQL Fiddle Demo
Note that, Since you don't have other columns to display under each column of course for each students, this query will display CourseID value for each course name, null if the student doesn't have this course. You should choose to display more appropriate column instead, like mark for example:
WITH CTE
AS
(
SELECT
k.name as firstname,
k.surname as lastname,
k.Email,
c.CourseName, dn.Mark
FROM Students k
JOIN StudentCourses dn ON dn.StudentID = k.StudentID
INNER JOIN Courses c ON dn.CourseID = c.CourseID
WHERE c.CourseName IN ('Course1','Course2','Course3')
)
SELECT *
FROM CTE AS c
PIVOT
(
MAX(Mark)
FOR CourseName IN ([Course1], [Course2], [Course3])
) u;
Updated SQL Fiddle Demo
This will give you:
| firstname | lastname | Email | Course1 | Course2 | Course3 |
|-----------|----------|-------|---------|---------|---------|
| StudentA | test | test | 19 | 20 | 15 |
| StudentB | test | test | 16 | 17 | 20 |
| StudentC | test | test | 20 | 19 | 15 |
Also note that, you should be careful about the columns you choose in the anchor query:
...
SELECT
k.name as firstname,
k.surname as lastname,
k.Email,
c.CourseName, dn.Mark
...
Because the PIVOT table operator will group by all the columns except the columns you listed and used to pivot:
MAX(CourseID)
FOR CourseName IN
So in your case, it will group by firstname, lastname, Email.
I think you can use a better query than yours and use MAX(CASE ...) to achieve your expected result like this:
SELECT k.name as firstname, k.surname as lastname, k.Email,
MAX(CASE WHEN c.CourseName = 'Course1' THEN 'Yes' ELSE 'No' END) As Course1,
MAX(CASE WHEN c.CourseName = 'Course2' THEN 'Yes' ELSE 'No' END) As Course2,
MAX(CASE WHEN c.CourseName = 'Course3' THEN 'Yes' ELSE 'No' END) As Course3
FROM Students k
JOIN StudentCourses dn ON dn.StudentID = k.StudentID
JOIN Courses c ON c.CourseID = dn.CourseID AND c.CourseName Like 'Course[1-3]'
GROUP BY k.name, k.surname, k.Email;

Sql Query to get data from associative table

I have to fetch data from two tables
(PK) = Primary Key
(FK) = Foreign Key
TABLE1- [STUDENTS]
s_id(PK) name other
1 a z
2 b z
3 c z
TABLE2- [CLASSES]
c_id(PK) class_name
1 5th
2 6th
3 7th
TABLE3- [STUDENT-CLASS]
id(PK) student_id(FK) class_id(FK)
1 1 1
2 1 2
3 2 1
4 2 2
5 3 1
6 1 3
I want to display students with current classes(Last assigned class)
tables relations is as
when student gets admitted it is assigned class 1
after 1 year a new record is inserted in [STUDENT-CLASS] table assigning new class to each or some student
I want to display like this
s_id name other [STUDENT-CLASS].Class_id [CLASSES].class_nam
1 a z 3 7th
2 b z 2 6th
3 c z 1 5th
Try something like this
Select S.studentid, s.name, s.other, c.classid, c.classname
from
(Select studentid, Max(Classid) as 'currentclassid' from StudentClassTable group by studentid) A
inner join StudentTable S on A.studentid = S.Studentid
inner join ClassTable C on A.CurrentClassid = C.Classid
The following query will do the job.
SELECT student_id, name, other, b.last_class_id, c.class_name
FROM STUDENTS a
LEFT JOIN (SELECT student_id, max(class_id) As last_class_id
FROM student_class
GROUP BY student_id) b ON a.student_id = b.student_id
LEFT JOIN CLASSES c ON c.class_id = b.last_class_id

Single query to count by category by joining multiple tables

I have the following 3 tables. I would like to write a single query to get a count of the number of courses enrolled in by a student for every difficulty level, and the total number of courses enrolled in as well. Students who have not enrolled should be listed too.
Students Table:
Student ID Student Name
1 Alice
2 Bob
3 Charlie
4 David
Courses Table:
Course ID Course Name Difficulty Level
1 Arithmetic 1
2 Advanced Calculus 3
3 Algebra 2
4 Trignometry 2
Enrollment Table:
Enrollment ID Student ID Course ID
1 1 1
2 1 3
3 1 4
4 2 2
5 2 3
6 2 4
7 3 3
Here's the expected output:
Output:
Student ID Student Name Total Courses Courses with Courses with Courses with
Enrolled In Difficulty Level 1 Difficulty Level 2 Difficulty Level 3
1 Alice 3 1 2 0
2 Bob 3 0 2 1
3 Charlie 1 0 1 0
4 David 0 0 0 0
I appreciate any help with this. I've tried a few different queries and am finding it hard to arrive at a single query that lists all students.
This will pull in all students regardless if they are registered for any courses or not
SELECT s.student_id, student_name, count(c.course_id)
, sum(case when difficulty_level = 1 then 1 else 0 end) as level1
, sum(case when difficulty_level = 2 then 1 else 0 end) as level1
, sum(case when difficulty_level = 3 then 1 else 0 end) as level1
FROM students s
left outer join enrollment e ON s.student_id = e.student_id
left outer join courses c ON e.course_id = c.course_id
GROUP BY s.student_id, student_name
select s.[Student ID]
, s.[Student Name]
, count(c.[Course ID])
, count(case when c.[Difficulty level] = 1 end)
, count(case when c.[Difficulty level] = 2 end)
, count(case when c.[Difficulty level] = 3 end)
from Students s
left join
Enrollment e
on e.[Student ID] = s.[Student ID]
left join
Courses c
on c.[Course ID] = e.[Course ID]
group by
s.[Student ID]
, s.[Student Name]
Well, not exactly what you want but it is similar to what you want. Any knowledgeable person can help me to make this query to return result in one row. Conceptually i don't think my query will be able to retrieve required records in one row.
select s.id,s.name,
count(c.id) as courses,
case c.diff
when 1 then count(c.diff) else 0
end as "level1",
case c.diff
when 2 then count(c.diff) else 0
end as "level2",
case c.diff
when 3 then count(c.diff) else 0
end as "level3"
from student s
left join enrollment e
on e.st_id=s.id
left join course c
on e.cr_id=c.id
group by s.id,s.name,c.diff
SQL FIDDLE
As you can see in the fiddle that for "alice" for example, the courses information is coming in different rows. For course with difficutly level 1 there is an separate row and similarly for others.
Conceptually when sql engine is selecting the rows then it will encounter only one course info so that in a row it will give information for only one course.
Can anyone tell how to make the result come in a single row for a student ?
SELECT s.id AS 'Student ID', s.name AS 'Student Name', COUNT(e.c_id) AS 'No of cource',
COALESCE(SUM( c.lavel = 1 ),0) AS 'Difficult lavel 1',
COALESCE(SUM( c.lavel = 2 ),0) AS 'Difficult lavel 2',
COALESCE(SUM( c.lavel = 3 ),0) AS 'Difficult lavel 3'
FROM students s
LEFT JOIN enrollment e ON s.id = e.s_id
LEFT JOIN courses c ON c.id = e.c_id
GROUP BY s.id,s.name;