How to count students and classes of every teacher in SQL Server? - sql

Assume 3 tables like this:
Teacher:
teacher_id, name, ...
Student:
student_id, teacher_id, ...
Class:
class_id, teacher_id, ...
Is there a SQL query to get how many students and classes are assigned to every teacher?
Result should be something like this:
teacher_id name students classes
t_001 AAA 3 2
t_002 BBB 1 2
...

Try something like this:
SELECT
t.teacher_id, t.name,
Classes = (SELECT COUNT(*) FROM Class c WHERE c.teacher_id = t.teacher_id),
Students = (SELECT COUNT(*) FROM Student s WHERE s.teacher_id = t.teacher_id)
FROM teacher t

with teacher_students as (
select t.teacher_id,
t.name,
count(s.student_id) as students
from Teacher t
left outer join Student s
on t.teacher_id = s.teacher_id
group by t.teacher_id, t.name
),
teacher_classes as (
select t.teacher_id,
count(c.class_id) as classes
from Teacher t
left outer join Class c
on t.teacher_id = c.teacher_id
group by t.teacher_id
)
select ts.teacher_id,
ts.name,
ts.students,
tc.classes
from teacher_students ts
join teacher_classes tc
on ts.teacher_id = tc.teacher_id

try this:
SELECT T.TEACHER_ID
, T.NAME,
, COUNT(C.TEACHER_ID ) AS CLASSES
, COUNT(S.TEACHER_ID ) AS STUDENTS
FROM TEACHER T
LEFT OUTER JOIN CLASS C
ON T.TEACHER_ID = C.TEACHER_ID
LEFT OUTER JOIN STUDENT S
ON T.TEACHER_ID = S.TEACHER_ID
GROUP BY T.TEACHER_ID, T.NAME

Related

How to extract rows from three tables

I know to ask this question is not appropriate.
Student table - ID, Name, GPA
Class table - ID, Title, Semester
Student_Class table - Student_ID, Class_ID, Student_Grade
From this table structure, how to extract the student name who attended class.title = ‘MATH101’ and class.semester = ‘FALL2018’.
Without much research I got to ask this question. How can I make a start on this?
Student_Class is join table which implements many-to-many relationship between Student and Class. So it just needed to join Student_Class with Student on student_id and Student_Class with Class on class_id.
select s.name
from Student s inner join Student_Class cs on s.id = cs.student_id
inner join Class c on cs.class_id = c.id
where c.title = ‘MATH101’ and c.semester = ‘FALL2018’.
You want to select the student name, so select from the student table. You only want to consider students who attended math in fall 2018, so add a where clause limiting the students accordingly.
This can achieved in various ways. One way to do this is
select name
from students
where id in
(
select student_id
from student_class
where class_id =
(
select id
from class
where title = 'MATH101'
and semester = 'FALL2018'
)
)
order by name;
Another is
select name
from students s
where exists
(
select null
from student_class sc
join class c on c.id = sc.class_id
where sc.student_id = s.id
and c.title = 'MATH101'
and c.semester = 'FALL2018'
)
order by name;

Sql query to find everything about the students who are not registered in a particular class

Suppose I have three tables Student, Class, Student_Class(linking table).
Table:Student
Student_ID
FirstName
LastName
Table:Class
Class_ID
Class_name
Table:Student_Class (Linking Table)
StudentClass_ID
Class_ID
Student_ID
Given a particular Class_ID, I want to know everything about the students who are not registered for that particular class.
I tried the following but it does not work. Lets say I try for class id = 3
SELECT DISTINCT Student.*
FROM Student
INNER JOIN Student_Class
ON Student.Student_ID = Student_Class.Student_ID
WHERE ((NOT (Student_Class.Class_ID)= 3))
Flow
Match all student and all classes
Student INNER JOIN Student_Class ON (Student.Student_ID = Student_Class.Student_ID)
Reject Students in particular class (Example 3)
(Student_Class.Class_ID <> 3)
Query:
SELECT Student.*
FROM Student INNER JOIN Student_Class
ON (Student.Student_ID = Student_Class.Student_ID)
WHERE (Student_Class.Class_ID <> 3);
In order to get all Students that they are not in any Class, please try below:
SELECT Student.*
FROM
(
SELECT Student.*
, Student_Class.Class_ID
FROM Student LEFT JOIN Student_Class ON Student.Student_ID = Student_Class.Student_ID
WHERE ((Student_Class.Class_ID <> 3) OR (Student_Class.Class_ID IS NULL))
) Student LEFT JOIN Class ON Student.Class_ID = Class.Class_ID
;
Data:
Result:
Below query will show all student that did not attend 1 or more classes, including the class name.
SELECT Student.Student_ID, Student.FirstName, Student.LastName,
Class.Class_ID, Class.Class_name
FROM Class, Student
WHERE NOT EXISTS
(
SELECT *
FROM Student_Class
WHERE Student_Class.Student_ID = Student.Student_ID AND
Student_Class.Class_ID = Class.Class_ID
)

How to use SQL Join operations with multiple tables

Assume 7 tables like this:
class_teacher_lesson
class_teacher_lession_ID, class_ID, teacher_ID, lesson_ID
class
class_ID, class
teacher
teacher_ID, teacher
lesson
lesson_ID, lesson
student
student_ID, class_ID, ..., ....
survey_answer
survey_answer_ID, survey_question_ID, class_teacher_lession_ID, survey_answer
survey_question
survey_question_ID, survey_question,
Result should be something like this:
I will get this with a loop:
LESSON MATH:
survey_question (where teacher_id = 11 and lesson_id = 13):
Total Students Total Students Answered Survey survey_answer AVG
155 45 79%
survey_question 2 (where teacher_id = 11 and lesson_id = 13):
Total Students Total Students Answered Survey survey_answer AVG
155 99 87%
LESSON ENGLISH:
survey_question (where teacher_id = 11 and lesson_id = 15):
Total Students Total Students Answered Survey survey_answer AVG
90 20 50%
survey_question 2 (where teacher_id = 11 and lesson_id = 15):
Total Students Total Students Answered Survey survey_answer AVG
90 25 34%
So far I've tried this:
SELECT teacher AS 'Teacher', class AS 'Class', lesson AS 'Lesson',
COUNT(student_ID) AS 'Number of students',
COUNT(enquete_antwoord_ID) AS 'Total students answered survey',
enquete_vraag AS 'Survey Question'
FROM Teacher
LEFT JOIN class_teacher_lesson
ON teacher.teacher_ID = class_teacher_lesson.teacher_ID
LEFT JOIN lesson
ON class_teacher_lesson.class_ID = lesson.lesson_ID
LEFT JOIN class
ON class_teacher_lesson.class_ID = class.class_ID
LEFT JOIN student
ON class.class_ID = student.class_ID
LEFT JOIN survey_answer
ON class_teacher_lesson.class_teacher_lesson_ID =
survey_answer.class_teacher_lesson
LEFT JOIN survey_question
ON survey_answer.survey_question_ID = survey_question.survey_question_ID
WHERE teacher.teacher_ID = '1158' AND lesson.lesson_ID = '449'
GROUP BY class.class, lesson.lesson, teacher.teacher,
survey_answer.survey_answer, survey_question
With this results:
Teacher: Class: Lesson: Number of students: Filled in: Survey_question:
t1 c1 math 54 54 question1?
t1 c1 math 24 0 NULL
t1 c1 math 22 0 NULL
The results should become like this:
Teacher: Class: Lesson: Number of students: Filled in: Survey_question:
t1 c1 math 90 54 question1?
I am assuming that enquete_vraag in your select and survey_question in your question are actually the same column and it is just something that has been missed in translation (otherwise the query would fail to to selecting columns not in the group by).
In which case all you actually need to do is remove survey_answer.survey_answer from your group by clause (You can also change some of your left joins to inner, which could improve performance.).
SELECT teacher AS 'Teacher',
class AS 'Class',
lesson AS 'Lesson',
COUNT(student_ID) AS 'Number of students',
COUNT(enquete_antwoord_ID) AS 'Total students answered survey',
enquete_vraag AS 'Survey Question',
100.0 * COUNT(enquete_antwoord_ID) / COUNT(student_ID) AS 'survey_answer AVG'
FROM Teacher
INNER JOIN class_teacher_lesson
ON teacher.teacher_ID = class_teacher_lesson.teacher_ID
INNER JOIN lesson
ON class_teacher_lesson.class_ID = lesson.lesson_ID
INNER JOIN class
ON class_teacher_lesson.class_ID = class.class_ID
LEFT JOIN student
ON class.class_ID = student.class_ID
LEFT JOIN survey_answer
ON class_teacher_lesson.class_teacher_lesson_ID = survey_answer.class_teacher_lesson
LEFT JOIN survey_question
ON survey_answer.survey_question_ID = survey_question.survey_question_ID
WHERE teacher.teacher_ID = '1158'
AND lesson.lesson_ID = '449'
GROUP BY class.class, lesson.lesson, teacher.teacher, survey_question
However (although this is entirely subjective) I'd suggest cleaning up your query using table aliases, and also not using single quotes to identify column aliases (to avoid confusion with literals), and (this is even more subjective) I have added the SQL-Server specific Alias = Column_name syntax, which I personally find much more readable (Perhaps some more eloquent reasons to switch are presented here by Aaron Bertrand).
SELECT t.teacher,
c.class,
l.lesson,
[Number of students] = COUNT(s.student_ID),
[Total students answered survey] = COUNT(sa.enquete_antwoord_ID),
[Survey Question] = sq.enquete_vraag,
[% Students Answered] = 100.0 * COUNT(sa.enquete_antwoord_ID) / COUNT(s.student_ID)
FROM Teacher t
INNER JOIN class_teacher_lesson ctl
ON t.teacher_ID = ctl.teacher_ID
INNER JOIN lesson l
ON ctl.class_ID = l.lesson_ID
INNER JOIN class c
ON ctl.class_ID = c.class_ID
LEFT JOIN student s
ON c.class_ID = s.class_ID
LEFT JOIN survey_answer sa
ON ctl.class_teacher_lesson_ID = sa.class_teacher_lesson
LEFT JOIN survey_question sq
ON sa.survey_question_ID = sq.survey_question_ID
WHERE t.teacher_ID = '1158'
AND l.lesson_ID = '449'
GROUP BY c.class, l.lesson, t.teacher, sq.survey_question;
Both of these solutions should give the same result, and I am not trying to force my preferences on anybody, I am just presenting options. It is just a matter of preference as to which you find more readable.
EDIT
I think you are getting cross joining due to joining multiple one-many relationships to the same "one", which is causing the null survey questions. I think moving the aggregates to subqueries may help:
SELECT t.teacher,
c.class,
l.lesson,
[Number of students] = ISNULL(s.Students, 0),
[Total students answered survey] = ISNULL(sq.Answers, 0),
sq.SurveyQuestion,
[% Students Answered] = 100.0 * ISNULL(sq.Answers, 0) / NULLIF(s.Students, 0)
FROM Teacher t
INNER JOIN class_teacher_lesson ctl
ON t.teacher_ID = ctl.teacher_ID
INNER JOIN lesson l
ON ctl.class_ID = l.class_ID
INNER JOIN class c
ON ctl.class_ID = c.class_ID
LEFT JOIN
( SELECT Class_ID, Students = COUNT(*)
FROM student s
GROUP BY Class_ID
) S
ON c.class_ID = s.class_ID
LEFT JOIN
( SELECT sa.class_teacher_lesson_ID,
sq.SurveyQuestion,
Answers = COUNT(*)
FROM survey_answer sa
LEFT JOIN survey_question sq
ON sa.survey_question_ID = sq.survey_question_ID
GROUP BY sa.class_teacher_lesson_ID, sq.SurveyQuestion
) sq
ON ctl.class_teacher_lesson_ID = sq.class_teacher_lesson_ID
WHERE t.teacher_ID = '1158'
AND l.lesson_ID = '449';
select survey_question, teacher_id, lesson_id ,
count(1) as Total_student_answer_survey,
(select count(1) from student) as total_student,
count(1)/(select count(1) from student) * 100 as survey_percentage
from survey_answer sa
inner join survey_question sq
on sa.survey_question_id = sq.survey_question_id
inner join class_teacher_lesson ctl
on sa.class_teacher_lesson_id = ctl.class_teacher_lesson_id
group by teacher_id, lesson_id, survey_question_id

writing subquery in the where clause

I have the following tables
student(student_id, name)
assignment(student_id, course_code, assignment number)
I'm trying to write a query to return those students who have submitted assignment number 1 for a particular course but not assignment number 2
I've written the following query but struggling to itso it returns results on per course basis. Any suggestions?
SELECT name, a.student_id, course_code
FROM assignment a INNER JOIN student s
on a.student_id = s.student_id
WHERE assignment_number = 1
AND assignment_number != 2
SELECT name, a.student_id, course_code
FROM assignment a INNER JOIN student s
on a.student_id = s.student_id
WHERE assignment_number = 1
AND not exists (select * from
assignment a2 inner join student s2
where a2.student_id = s2.student_id
and s2.student_id = s.student_id
and a2.assignment_number = 2)
Here's the fiddle to see it in action:
http://sqlfiddle.com/#!2/48997/2
SELECT
s.name,
s.student_id,
a.course_code
FROM
assignment a
INNER JOIN
student s
ON
a.student_id = s.student_id
WHERE
assignment_number in( 1,2 )
GROUP BY
s.name,
s.student_id,
a.course_code
HAVING max(assignment_number) = 1
List of students that have completed assignment 1, but not completed assignment 2:
select s.name, a.student_id, a.course_code
from assignment a
inner join student s on a.student_id = s.student_id
where a.student_id in
(Select student_id from assignment
where assignment_id = 1) -- All students that submitted Assignment 1
and a.assignment_id !=2

SQL Query to Count() multiple tables

I have a table which has several one to many relationships with other tables. Let's say the main table is a person, and the other tables represent pets, cars and children. I would like a query that returns details of the person,the number of pets, cars and children they have e.g.
Person.Name Count(cars) Count(children) Count(pets)
John Smith 3 2 4
Bob Brown 1 3 0
What is the best way to do this?
Subquery Factoring (9i+):
WITH count_cars AS (
SELECT t.person_id
COUNT(*) num_cars
FROM CARS c
GROUP BY t.person_id),
count_children AS (
SELECT t.person_id
COUNT(*) num_children
FROM CHILDREN c
GROUP BY t.person_id),
count_pets AS (
SELECT p.person_id
COUNT(*) num_pets
FROM PETS p
GROUP BY p.person_id)
SELECT t.name,
NVL(cars.num_cars, 0) 'Count(cars)',
NVL(children.num_children, 0) 'Count(children)',
NVL(pets.num_pets, 0) 'Count(pets)'
FROM PERSONS t
LEFT JOIN count_cars cars ON cars.person_id = t.person_id
LEFT JOIN count_children children ON children.person_id = t.person_id
LEFT JOIN count_pets pets ON pets.person_id = t.person_id
Using inline views:
SELECT t.name,
NVL(cars.num_cars, 0) 'Count(cars)',
NVL(children.num_children, 0) 'Count(children)',
NVL(pets.num_pets, 0) 'Count(pets)'
FROM PERSONS t
LEFT JOIN (SELECT t.person_id
COUNT(*) num_cars
FROM CARS c
GROUP BY t.person_id) cars ON cars.person_id = t.person_id
LEFT JOIN (SELECT t.person_id
COUNT(*) num_children
FROM CHILDREN c
GROUP BY t.person_id) children ON children.person_id = t.person_id
LEFT JOIN (SELECT p.person_id
COUNT(*) num_pets
FROM PETS p
GROUP BY p.person_id) pets ON pets.person_id = t.person_id
you could use the COUNT(distinct x.id) synthax:
SELECT person.name,
COUNT(DISTINCT car.id) cars,
COUNT(DISTINCT child.id) children,
COUNT(DISTINCT pet.id) pets
FROM person
LEFT JOIN car ON (person.id = car.person_id)
LEFT JOIN child ON (person.id = child.person_id)
LEFT JOIN pet ON (person.id = pet.person_id)
GROUP BY person.name
I would probably do it like this:
SELECT Name, PersonCars.num, PersonChildren.num, PersonPets.num
FROM Person p
LEFT JOIN (
SELECT PersonID, COUNT(*) as num
FROM Person INNER JOIN Cars ON Cars.PersonID = Person.PersonID
GROUP BY Person.PersonID
) PersonCars ON PersonCars.PersonID = p.PersonID
LEFT JOIN (
SELECT PersonID, COUNT(*) as num
FROM Person INNER JOIN Children ON Children.PersonID = Person.PersonID
GROUP BY Person.PersonID
) PersonChildren ON PersonChildren.PersonID = p.PersonID
LEFT JOIN (
SELECT PersonID, COUNT(*) as num
FROM Person INNER JOIN Pets ON Pets.PersonID = Person.PersonID
GROUP BY Person.PersonID
) PersonPets ON PersonPets.PersonID = p.PersonID
Note, that it depends on your flavour of RDBMS, whether it supports nested selects like the following:
SELECT p.name AS name
, (SELECT COUNT(*) FROM pets e WHERE e.owner_id = p.id) AS pet_count
, (SELECT COUNT(*) FROM cars c WHERE c.owner_id = p.id) AS world_pollution_increment_device_count
, (SELECT COUNT(*) FROM child h WHERE h.parent_id = p.id) AS world_population_increment
FROM person p
ORDER BY p.name
IIRC, this works at least with PostgreSQL and MSSQL. Not tested, so your mileage may vary.
Using subselects not very good practice, but may be here it will be good
select p.name, (select count(0) from cars c where c.idperson = p.idperson),
(select count(0) from children ch where ch.idperson = p.idperson),
(select count(0) from pets pt where pt.idperson = p.idperson)
from person p
You could do this with three outer joins:
SELECT
Person.Name,
sum(case when cars.id is not null then 1 else 0 end) car_count,
sum(case when children.id is not null then 1 else 0 end) child_count,
sum(case when pets.id is not null then 1 else 0 end) pet_count
FROM
Person
LEFT OUTER JOIN
cars on
Person.id = cars.person_id
LEFT OUTER JOIN
children on
Person.id = children.person_id
LEFT OUTER JOIN
pets on
Person.id = pets.person_id
GROUP BY
Person.Name
I belive that Oracle now supports the case when syntax, but if not you could use a decode.
You'd need to include multiple count statements in the query. Off the top of my head,
SELECT p.Name, COUNT(DISTINCT t.Cars), COUNT(DISTINCT o.Children), Count(DISTINCT p.Pets)
FROM Person p
INNER JOIN Transport t ON p.ID = t.PersonID
LEFT JOIN Offspring o ON p.ID = o.PersonID
LEFT JOIN Pets p ON p.ID = o.OwnerID
GROUP BY p.Name
ORDER BY p.Name