How to query data that is ommitted from the results - sql

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;

Related

Between genres, which type of movies seems to be more rated by age group?

how can i join three tables, find the most rated genre from genres and group them in age.
i have already created the tables
i did something like this, but i know is wrong
SELECT count(CASE WHEN (genre and age < 40 ) THEN rating ELSE null END),
count(CASE WHEN (genre age < 40 ) THEN rating ELSE null END)
FROM user
INNER JOIN data ON data.userid = user.userid
RIGHT JOIN item ON item.movieid = data.itemid
If I understand you question correctly, I don't think you need a CASE statement.
SELECT age, genre, count(rating) total_rating
from User u
inner join Data d on u.userId = d.userId
inner join Item i on i.itemId = d.itemId
WHERE age >= 40
Group by age, genre;
I did also try to use a case statement, but it's a bit more ugly.
SELECT age,
genre,
sum (CASE WHEN age>=40
then 1
ELSE 0
end) as 'UNDER40'
from User u
inner join Data d on u.userId = d.userId
inner join Item i on i.itemId = d.itemId
where age >=40
Group by age, genre
db fiddle

ORACLE SQL check if the row number of the table is n, then perform a join

TABLE student: ID, ID2, NAME, AGE
TABLE class: ID, CLASS_NAME, some other columns
TABLE school: ID2, some other columns.
I am trying to perform below in Oracle SQL:
If the count of the records in TABLE "student" with age>5, is 1,
join the student table with "CLASS" table by "ID", else, join the student table with "school" table by "ID2".
I found I cannot put count in where clause. Can someone help?
I would use window functions:
select s.*, . . .
from (select s.*, sum(case when age > 5 then 1 else 0 end) over () as cnt5
from students s
) s left join
class c
on c.id = s.id and cnt5 = 1 left join
school sch
on sch.id2 = s.id2 and cnt5 <> 1
where c.id is not null or sch.id is not null
you can use left join with case
select s.*, case when age > 5 then COALESCE (c.ID,scl.ID2) as id
from student s
left join class c on s.ID=c.ID
left join school scl on s.ID2=scl.ID2
As Gordon said window functions are an option, but this is also a rare occasion where you can deliberately perform a cross join:
select s.*
,...
from students s
left join
(
select count(*) as RECORD_COUNT
from students
where age > 5
) sub
on 1 = 1
left join class c
on s.id = c.id
and sub.record_count = 1
left join school h
on s.id2 = h.id2
and c.id is null

Selecting exact value in SQL

I am stuck on a query and decided to ask for help here.
I have 2 tables Students and Values. In students I have the Name and in Values I have the grade.
Let's suppose we have 3 students.
**X** 7,8,10
**Y** 6,9,7
**Z** 7
How can i select the students with the grade EXACTLY "7"?
I tried:
SELECT WHERE grade = 7 But it takes in the consideration the students who have "7" but also other grades too.
I think this problem is tricky, can someone give a hint?
One way is to use a comparison between unconditional and conditional counts:
select s.student_name
from students s join grades g on s.student_id = g.student_id
group by student_id
having count(*) = count(case when g.value = 7 then 1 end)
;
(guessing at some column names along the way)
How it works: After joining the two tables, the rows are grouped by student_id. Then COUNT(*) counts all the grades, and the conditional count counts the grades equal to 7. The query returns the student names when the two counts are equal (meaning all the grades are 7).
Another solution (less efficient):
select s.student_name
from students s inner join grades g on s.student_id = g.student_id
where g.grade = 7
minus
select s.student_name
from students s inner join grades g on s.student_id = g.student_id
where g.grade != 7 or g.grade is null
;
One simple way is to look at the task like this: Find students that have no grade other than seven.
select *
from students
where not exist
(
select *
from grades
where grades.student_id = students.student_id
and grades.grade <> 7
);
or
select *
from students
where student_id not in
(
select student_id
from grades
where grade <> 7
);
I think that an INNER JOIN followed by an exclusive LEFT JOIN would do the trick. Something like this:
WITH
seven AS (
SELECT a.id, a.name, b.grade
FROM student_name a
INNER JOIN student_grade b
ON a.id = b.id
WHERE b.grade = '7'
)
, not_seven AS (
SELECT a.id, a.name, a.grade
FROM seven a
LEFT JOIN student_grade
ON a.id = b.id
WHERE b.id IS NULL
AND b.grade <> '7'
)
SELECT * FROM not_seven;
what about using count and sum? Would that work?
select s.student, Count(v.grade), sum(v.grade)
From student as s
join values as v on v.student = s.student
group by student
having sum(v.grade) = 7 and count(v.grade) = 1

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

Select one record from two tables in Oracle

There are three tables:
A table about students: s41071030(sno, sname, ssex, sage, sdept)
A table about course: c41071030(cno, cname, cpno, credit)
A table about selecting courses: sc41071030(sno, cno, grade)
Now, I want select the details about a student whose sdept='CS' and he or she has selected the most courses in department 'CS'.
As with any modestly complex SQL statement, it is best to do 'TDQD' — Test Driven Query Design. Start off with simple parts of the question and build them into a more complex answer.
To find out how many courses each student in the CS department is taking, we write:
SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno;
We now need to find the largest value of NumCourses:
SELECT MAX(NumCourses) MaxCourses
FROM (SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
Now we need to join that result with the sub-query, so it is time for a CTE (Common Table Expression):
WITH N AS
(SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
SELECT N.Sno
FROM N
JOIN (SELECT MAX(NumCourses) MaxCourses FROM N) M
ON M.MaxCourses = N.NumCourses;
And we need to get the student details, so we join that with the student table:
WITH N AS
(SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
SELECT S.*
FROM s41071030 S
JOIN N ON N.Sno = S.Sno
JOIN (SELECT MAX(NumCourses) MaxCourses FROM N) M
ON M.MaxCourses = N.NumCourses;
Lightly tested SQL: you were warned. To test, run the component queries, making sure you get reasonable results each time. Don't move on to the next query until the previous one is working correctly.
Note that the courses table turns out to be immaterial to the query you are solving.
Also note that this may return several rows if it turns out there are several students all taking the same number of courses and that number is the largest number that any student is taking. (So, if there are 3 students taking 7 courses each, and no student taking more than 7 courses, then you will see 3 rows in the result set.)
Aggregate sc41071030 rows to get the counts.
Join the results to s41071030 to:
filter rows on sdept;
get student details;
RANK() the joined rows on the count values.
Select rows with the ranking of 1.
WITH
aggregated AS (
SELECT
sno,
COUNT(*) AS coursecount
FROM
sc41071030
GROUP BY
sno
),
ranked AS (
SELECT
s.*,
RANK() OVER (ORDER BY agg.coursecount DESC) AS rnk
FROM
s41071030 s
INNER JOIN aggregated agg ON s.sno = agg.sno
WHERE
s.sdept = 'CS'
)
SELECT
sno,
sname,
ssex,
sage,
sdept
FROM
ranked
WHERE
rnk = 1
;