How to use SQL Join operations with multiple tables - sql

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

Related

How to query data that is ommitted from the results

So I have the following information
Output
Diabetic Schools studentcount
false 9010 180
true 9010 3
false 9012 245
true 9012 4
Query
Select s.diabetic as diabetic, sch.buildingid as Schools,
count(distinct s.studentnmr) as Studentcount
from student s
inner join studentschool ss.studentid = s.studentid
inner join school sch.id = ss.schoolid
order by sch.id
I want
Diabetic addresse studentcount calculation
true 9010 3 1,64 %
true 9012 4 1,61 %
where calculation is
( sum(diabetic=true)/sum(total number of students of the school) )*100
Additional tip there is another field called
diabeticdate
which has a date when diabetic is true.
My problem
when I select
select sum(Case when s.diabetic is null then 1 else 0 end) AS notD
I get obviously nothing next to the record diabetic - True status
How do I work around this
note: if you have a better title for the question, please suggest!
You can use conditional aggregation in order to show one row per school with their diabetic rates:
select
sch.buildingid as Schools,
count(distinct s.studentnmr) as Studentcount
count(distinct case when s.diabetic then s.studentnmr end) as Diabeticcount,
count(distinct case when s.diabetic then s.studentnmr end) /
count(distinct s.studentnmr) * 100 as rate
from student s
inner join studentschool on ss.studentid = s.studentid
inner join school on sch.id = ss.schoolid
group by sch.buildingid
having count(distinct case when s.diabetic then s.studentnmr end) > 0
order by sch.buildingid;
Remove the HAVING clause, if you also want to see schools without diabetics.
you may try below by using over()
with t1 as
(
Select s.diabetic as diabetic, sch.buildingid as Schools,
count(distinct s.studentnmr) as Studentcount
from student s
inner join studentschool ss.studentid = s.studentid
inner join school sch.id = ss.schoolid
order by sch.id
),
t2 as
(
select case when Diabetic='true' then Schools end as addresse,
case when when Diabetic='true' then studentcount end as studentcount,
((case when when Diabetic='true' then studentcount end)::decimal/(sum(studentcount) over())) *100 as calculation
) select * from t2
You can use the window function SUM OVER to get the total number of students. Window functions run over the results you already have, a post aggregation so to say :-)
select
s.diabetic as diabetic,
sch.buildingid as Schools,
count(distinct s.studentnmr) as Studentcount,
count(distinct s.studentnmr)::decimal /
sum(count(distinct s.studentnmr)) over (partition by sch.buildingid) * 100 as rate
from student s
inner join studentschool on ss.studentid = s.studentid
inner join school on sch.id = ss.schoolid
group by sch.buildingid, s.diabetic
order by sch.buildingid, s.diabetic;

Count() on many to many relationship

I have a Students table and a Language table. They form a many to many relationship using a pivot table Languages_Student.
Is there a way of getting the student which has the biggest amount of languages in common with another student?
I'm not quite sure how to combine COUNT() with some kind of select. This is what I'm working with now:
select * from students student1
inner join languages_student ls1
on student1.id = ls1.student_id
inner join languages l1
on l1.id = ls1.language_id
inner join languages_student ls2
on l1.id = ls2.language_id
inner join students student2
on ls2.student_id = student2.id
where student1.id = 65
group by 16
I'm trying to get the student with biggest amount of languages in common with the student with id 65.
Any ideas?
You can do this using a self-join. Join the junction table to itself on the language column; then aggregate by the student column to get the number in common:
select ls.student_id, count(*) as NumInCommon
from languages_student ls join
languages_student ls65
on ls.language = ls65.language and ls65.student_id = 65 and
ls65.student_id <> ls.student_id
group by ls.student_id
order by count(*) desc;
select ls.student_id, count(ls.language_id) as common
from languages_student ls
where ls.id in (
select l.id
from students s
inner join languages_student ls
on s.id = ls.student_id
inner join languages l
on l.id = ls.language_id
where s.id = 65 )
order by count(ls.language_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

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

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

How to get common category among columns in SQL

Consider the following schema for SQL:
Student (StudID, StudName, DeptID, Age, Gpa);
Course (CourseID, CourseName, InstructorID);
Department (DeptID, DeptName, Location);
Instructor (InstructorID, InstructorName, DeptID);
Section (SectionID, SectionName, Time, RoomID, CourseID, InstructorID);
Room (RoomID, RoomName, Location);
Enrolled (StudID, SectionID);
Q: How to find the names of all sections that either meet in common room or have five or more students enrolled?
Well I am not sure if it will work:)
Common names;
select st.StudName as names from Student as st inner join Departmant as d
on st.DeptID = d.DeptID inner join Instructor as i
on i.DeptID = d.DeptID inner join Course as c
on c.InstructorID = i.InstructorID inner join Section as s
on s.InstructorID = i.InstructorID inner join Room as r
on r.RoomID = s.RoomID inner join Enrolled as e
on e.StudID = st.StudID;
More than 5 student enrolled(Something experimental:);
select st.StudName as names from Student as st inner join Departmant as d
on st.DeptID = d.DeptID inner join Instructor as i
on i.DeptID = d.DeptID inner join Course as c
on c.InstructorID = i.InstructorID inner join Section as s
on s.InstructorID = i.InstructorID inner join Room as r
on r.RoomID = s.RoomID inner join Enrolled as e
on e.StudID = st.StudID where count(e.StudID = st.StudID)>4;
If you are using SQL-Server you can write the query like this(I didn't tested it. So I can't say it works 100% but I hope it gives you an idea):
SELECT SectionName
FROM Section
WHERE SectionID IN --Students in common room.
(
SELECT SectionID FROM Section
INNER JOIN Instructor ON Section.SectionID = Instructor.InstructorID --Section to which the Instructor belongs
INNER JOIN Department ON Department.DeptID = Instructor.DeptID --Department to which the Instructor belongs
INNER JOIN Room ON Room.Location = Department.Location --Room to which the Department belongs
)
OR --Student Enrollment greater than 5.
(
(SELECT COUNT(StudID) FROM Student
INNER JOIN Enrolled ON Student.StudID = Enrolled.StudID
INNER JOIN Section ON Section.SectionID = Enrolled.SectionID) >= 5
)
Q:6. Find the names of all sections that either meet in room New-8 or have five or more students enrolled.
Answer:
select sectionName
from student as S, Enrolled as E, Section as Se
where S.studId=E.studId AND E.sectionID=se.SectionID
group by sectionName
having count(*)>=3
UNION
SELECT sectionName
FROM SECTION S, ROOM R
WHERE S.RoomID=R.ROOMID AND R.RoomName='new2'
this is the correct query i have run it on Db