Add default for missing rows (combinations of students and subjects) - sql

I have a table which holds grades of students in some subjects.
STUDENT_GRADES
id | studentid | subjectid | grade
-----------------------------------
1 | 1 | 1 | A+
2 | 1 | 2 | A
3 | 1 | 3 | A-
4 | 2 | 2 | A
5 | 2 | 3 | A-
6 | 3 | 2 | B
7 | 4 | 3 | B+
and I have another table which holds subject list.
SUBJECTS
subjectid | subjectname
-----------------------------
1 | Maths
2 | Science
3 | History
Now I have to retrieve result in the following format.
FORMATTED_GRADES
studentid | subjectid | grade
-----------------------------------
1 | 1 | A+
1 | 2 | A
1 | 3 | A-
2 | 1 | fail
2 | 2 | A
2 | 3 | A-
3 | 1 | fail
3 | 2 | B
3 | 3 | fail
4 | 1 | fail
4 | 2 | fail
4 | 3 | B+
The FORMATTED_GRADES contains each student from STUDENT_GRADES and his/her grade in each subject. If there is no grade of a student in a subject in STUDENT_GRADES, then the grade of the student in that subject should be fail.

You most probably have another table students with studentid as primary key. Use that in a CROSS JOIN to form a Cartesian product with one row for each combination of student and subject:
SELECT student_id, subject_id, COALESCE(g.grade, 'fail') AS grade
FROM students t
CROSS JOIN subjects u
LEFT JOIN student_grades g USING (student_id, subject_id);
Most importantly, this includes students that failed in all subjects. If there is no entry in student_grades at all, the attempt to distill unique students from student_grades (like #Gordon demonstrates) will miss out on those.
Assuming that subjects has no column named student_id and students has no column subject_id. Else you must use the more explicit join syntax with ON instead of the shortcut with USING.
When joining with the USING clause, only one instance of the (necessarily identical) join columns is in the output table, hence we do not need table qualification in the SELECT list (even if that would be allowed anyway).
Also assuming that student_grades.grade is a character data type. Else you need to cast explicitly to get compatible data types in COALESCE. Like: COALESCE(g.grade::text, 'fail')

Use cross join to generate the rows, and left join and coalesce() to get the data you want:
select st.student_id, su.subject_id,
coalesce(sg.grade, 'fail') as grade
from (select distinct student_id from student_grades) st cross join
subjects su left join
student_grades sg
on sg.student_id = st.student_id and sg.subject_id = su.subject_id

Related

Using count my tuples are coming out as 1's when a 0 should be present?

I am trying to count how many times each ID appears in a specific table as follows.
Members
ID | NAME
1 | Bob
2 | John
3 | Sally
4 | Hannah
Department
ID | DEPT
1 | Math
1 | English
3 | Math
4 | Math
4 | English
4 | Science
I would like to display a table as follows:
ID | Name | Count(Dept)
1 | Bob | 2
2 | John | 0
3 | Sally | 1
4 | Hannah | 3
I have made several attempts at this my most recent one being:
select owner
from table
where (select count(distinct letter) from table group by owner) in
(select max(count(distinct letter)) from table group by owner);
I just cannot seem how to get the 0 results to show properly. Honestly, i'm kinda confused as to how to use on ... when you don't want to basically preform a natural join.
edit: I have edited the question to properly show what I am trying to ask. Sorry for any confusion.
When joining tables it is very important to reference WHICH TABLE each column comes from. To help abbreviate the overall SQL a "table alias" may be defined and used instead of the full table name.
select m.id, m.name, count(d.id) as count_d
from members m
left outer join department d on m.id = d.id
group by m.id, m.name
;
In the above I have used a LEFT OUTER JOIN. Once you have that outer join defined there may be some rows where no row from department is matched to a row in membesr and you therefore would get NULLs in all columns related to departmenton that row.
The COUNT() function increments by 1 for every NON-NULL value it encounters, so what you include inside the parentheses is important. COUNT(*) will increment for every row no matter what. COUNT(d.id) may give zeros because some rows might be NULL in that column.
SEE this SQL Fiddle
Results:
| ID | NAME | COUNT_D |
|----|--------|---------|
| 1 | Bob | 2 |
| 2 | John | 0 |
| 3 | Sally | 1 |
| 4 | Hannah | 3 |

Join 2 tables and Count the number of occurrence specific field in SQL

I have 2 tables , "T_COMMON_COUNTRY" and "T_HEI_STUDENTDATA." using left join I joined these tables
this is my query
SELECT
[T_COMMON_COUNTRY].[COUNTRY_ID],
[T_COMMON_COUNTRY].[COUNTRY],
[T_HEI_STUDENTDATA].[STUDENT_ID]
FROM ([T_COMMON_COUNTRY]
LEFT JOIN [T_HEI_STUDENTDATA]
ON [T_COMMON_COUNTRY].[COUNTRY] = [T_HEI_STUDENTDATA].[STDCOUNTRY])
now I' getting view like this
| Country ID | County | Student ID |
| 1 | USA | 12 |
| 1 | USA | 5 |
| 2 | UK | 11 |
| 2 | UK | 2 |
I want Count the number of students (Student_IDs) relate to a country ,
I want get a view exactly like below
| Country ID | County | Students |
| 1 | USA | 2 |
| 2 | UK | 2 |
Use COUNT function to generate countrywise student count
Try this:
SELECT C.[COUNTRY_ID], C.[COUNTRY], COUNT(S.[STUDENT_ID]) AS StudentCount
FROM [T_COMMON_COUNTRY] C
LEFT JOIN [T_HEI_STUDENTDATA] S ON C.[COUNTRY] = S.[STDCOUNTRY]
GROUP BY C.[COUNTRY_ID], C.[COUNTRY];
Use COUNT function to count the number of students GROUP BY Country_ID and Country.
SELECT
[T_COMMON_COUNTRY].[COUNTRY_ID],
[T_COMMON_COUNTRY].[COUNTRY],
COUNT([T_HEI_STUDENTDATA].[STUDENT_ID]) AS Students
FROM ([T_COMMON_COUNTRY]
LEFT JOIN [T_HEI_STUDENTDATA]
ON [T_COMMON_COUNTRY].[COUNTRY] = [T_HEI_STUDENTDATA].[STDCOUNTRY])
GROUP BY [T_COMMON_COUNTRY].[COUNTRY_ID], [T_COMMON_COUNTRY].[COUNTRY]

How to get number of students per course in sql?

So I have these 3 tables:
t_student which looks like this:
STUDENT_ID| FIRST_NAME |LAST_NAME
-----------------------------------
1 | Ivan | Petrov
2 | Ivan | Ivanov
3 | Georgi | Georgiev
t_course which looks like this:
course_id | NAME |LECTURER_NAME
-----------------------------------
1 | Basics | Vasilev
2 | Photography| Loyns
t_enrolment which looks like this:
enrolment_id| student_fk |course_fk | Avarage_grade
-------------------------------------------------------
1 | 1 | 1 |
2 | 3 | 1 |
3 | 4 | 1 |
4 | 2 | 1 |
5 | 1 | 2 | 5.50
6 | 2 | 2 | 5.40
7 | 5 | 2 | 6.00
I need to make 'select' statement and present the number of students per course. The result should be:
Count_students | Course_name
-----------------------------
4 | Basics
3 | Photography
Select all courses from your course Table, join the enrolment table and group by your course id. With count() you can select the number of Students
SELECT MAX(t_course.NAME) AS Course_name, COUNT(t_enrolment.student_fk) AS Count_students
FROM t_course
LEFT JOIN t_enrolment ON t_enrolment.course_fk = t_course.course_id
GROUP BY t_course.course_id;
If you want to select the same student in one course only once (if more then one enrolment can happen) you can use COUNT(DISTINCT t_enrolment.student_fk)
UPDATE
To make it working not only in mySQL I added an aggregate function to the name column.
Depending on the SQL database you are using you will have to add quotes or backticks.
Is this your homework?
select count(*) Count_students, c.name as course_name from t_enrolment e, t_course c group where e.course_fk = c.course_id by c.name
You need a select statement with a join to the couse table (for the Course_name). Group by 't_course'.name to use the COUNT(*) function
this will work:
SELECT COUNT(*) AS Count_students, c.NAME AS Course_name
FROM t_enrolment e
JOIN course c
ON e.course_fk = c.course_id
GROUP BY c.NAME
More information
Count function
Group by
Join

JOIN where row may not exist in one table

I have two tables:
students
+-------+------+
| id | name |
+-------+------+
| 1 | Bob |
+-------+------+
| 2 | Sam |
+-------+------+
and
courses
+----+------------+---------+--------+
| id | student_id | teacher | period |
+----+------------+---------+--------+
| 1 | 1 | Mr. X | 1 |
+----+------------+---------+--------+
| 2 | 1 | Ms. Y | 2 |
+----+------------+---------+--------+
| 3 | 2 | Mr. X | 2 |
+----+------------+---------+--------+
| 4 | 2 | Ms. Y | 3 |
+----+------------+---------+--------+
And this is the result I need from these two tables:
list of students and period 1 teacher
+------------+------+-----------------+
| student_id | name | period 1 teacher|
+------------+------+-----------------+
| 1 | Bob | Mr. X |
+------------+------+-----------------+
| 2 | Sam | null |
+------------+------+-----------------+
Okay, I need a list of students and the teacher they have for a certain period (in this case, period 1). They may, however, have no teacher listed for that period in the courses table, in which case I want 'null' for that column on that student (as above with 'Sam').
The closest I have is this:
SELECT students.id,students.name,courses.teacher
FROM students
LEFT JOIN courses ON students.id = courses.student_id AND courses.period = '1'
But I only ever get back rows that exist in BOTH tables (in this example, only the 'Bob' student would be returned since 'Sam' has no period 1 teacher.
I feel certain it is something simple, but my Google-fu has failed me thus far.
Can you try this:
SELECT students.id,students.name,courses.teacher
FROM students
LEFT JOIN (select * from courses WHERE courses.period = '1') courses
ON students.id = courses.student_id
if I understand correctly and according to the context is to appear only the student BOB.
Assuming it to be true, replace the LEFT for INNER what will work.
SELECT students.id,students.name,courses.teacher
FROM students
INNER JOIN courses ON students.id = courses.student_id AND courses.period = '1'
I hope it is useful

How to GROUP BY into separate columns

I have 3 tables:
The first one contains information about persons. The relevant column is the personID.
The second contains exercises a person can do. There are for example 3 exercises with an exID.
The third contains the points a person (personID) reached in an exercise (exID). So each row here stands for an examination a person has taken. But not everyone need to have taken every exam.
What I would like to have is a result with the columns personID, exam_one, exam_two, exam_three, ...(ongoing, depending on how many exams there are). And each row of the result should contain the personID and the points from the respective exam.
For exams not taken there should be NULL or something.
Example for table persons:
personID | Name | ...
-------------------
1 | Max |
2 | Peter |
Example for exercises table:
exID | exName | maxPoints | ...
-------------------------------
1 | exam1 | 20
2 | exam2 | 25
3 | exam3 | 20
Example for points table:
personID (fkey) | exID (fkey) | points
----------------------------------------
1 | 1 | 12.5
1 | 3 | 10
2 | 1 | 5
2 | 2 | 8.5
2 | 3 | 10
Wished result:
personId | exam1 | exam2 | exam3
------------------------------------
1 | 12.5 | NULL | 10
2 | 5 | 8.5 | 10
Is there a way to do this? I use PostgreSQL
You can use something like the following:
select p.personId,
sum(case when e.exname = 'exam1' then t.points end) Exam1,
sum(case when e.exname = 'exam2' then t.points end) Exam2,
sum(case when e.exname = 'exam3' then t.points end) Exam3
from persons p
left join points t
on p.personID = t.personID
left join exercises e
on t.exid = e.exid
group by p.personid
See SQL Fiddle with Demo