How to get number of students per course in sql? - 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

Related

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

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

how to select unique records from a table based on a column which has distinct values in another column

I have below table SUBJ_SKILLS which has records like
TCHR_ID | LINE_NBR | SUBJ | SUBJ_TYPE
--------| ------- | ---------- | ----------
1 | 1 | Maths | R
1 | 2 | 101 | U
2 | 1 | BehaviourialTech | U
3 | 2 | Maths | R
4 | 1 | RegionalLANG | U
5 | 3 | ForeignLANG | U
5 | 4 | Maths | R
6 | 2 | Science | R
7 | 1 | 101 | U
7 | 3 | Physics | R
..
..
I am trying to retrieve records like below (i.e. single teacher who taught multiple different subjects)
TCHR_ID | LINE_NBR | SUBJ | SUBJ_TYPE
--------| ------- | ---------- | ----------
5 | 3 | ForeignLANG | U
5 | 4 | Maths | R
7 | 1 | 101 | U
7 | 3 | Physics | R
1 | 1 | Maths | R
1 | 2 | 101 | U
Here, the line numbers are unique, means that TCHR_ID:5 taught Physics (which was LINE_NBR=1, but was removed later). So, the LINE_NBR are not updated and stay as is.
i also have a look up table (SUBJ_LKUP) for subject and their categories/type like below ('R' for Regular subject and 'U' for Unique subject )
SUBJ | SUBJ_TYPE
----------------- | ------------
Maths | R
Physics | R
ForeignLANG | U
101 | U
Science | R
BehaviourialTech | U
RegionalLANG | U
My approach to resolve this was to create a table which have 2 records for Teacher and use another query on base table (SUBJ_SKILLS) and new table to filter out distinct records. I came up with below queries..
Query-1:
create table tchr_with_2_subj as select SS.TCHR_ID
from SUBJ_SKILLS SS, SUBJ_LKUP SL
where SS.SUBJ = SL.SUBJ
and SL.SUBJ_TYPE IN ('R', 'U') AND SS.TCHR_ID IN
(select SS.TCHR_ID from SUBJ_SKILLS SS)
GROUP BY SS.TCHR_ID HAVING COUNT(*) = 2)
Query-2:
select SS.TCHR_ID from SUBJ_SKILLS SS, tchr_with_2_subj tw2s
where SS.TCHR_ID = tw2s.TCHR_ID
GROUP BY SS.TCHR_ID,SS.SUBJ_TYPE HAVING COUNT(*) > 1)
Question:
1)'IN' condition in Query-1 is causing problems and pulling wrong records.
2) Is there a better way to write query to pull matching records using a single query (i.e. instead of creating a table)
Could someone help me on this pls.
For the answer to your original question, I would use window functions:
select ss.*
from (select ss.*,
min(subj) over (partition by tchr_id) as mins,
max(subj) over (partition by tchr_id) as maxs
from SUBJ_SKILLS ss
) ss
where mins <> maxs;
It is unclear how the subject type fits in, but if you need to include that, similar logic will work.
Your second table can be obtained from your first table with:
select ss.*
from
subj_skills as ss
inner join (
select tchr_id
from subj_skills
group by tchr_id
having count(*) > 1
) as mult on mult.tchr_id=ss.tchr_id;
I'd use analytic functions here, asomething like:
select tchr_id, line_nbr, subj, SUBJ_TYPE
from (select count(distinct subj) over (partition by tchr_id) as grp_cnt,
s.*
from subj_skills s)
where grp_cnt > 1
If you need to filter out invalid records, you can do it in the inner query. If a teacher cannot teach the same subject multiple times (the req 'multiple different subjects' can be translated to 'multiple subjects'), then I'd rather use count(*) instead of count(distinct subj).

Why is a WHERE clause to exclude rows in SQLite not working as expected?

I am trying to exclude a list of Names in an
I have the table Names
id| Name | Surname
---------------------
1 | Michael | Kane
2 | Torben | Dane
3 | Dinge | Chain
4 | Django | Fain
5 | Juliett | Bravo
And i have the Table Excludes
id| Name
-----------
1 | Michael
2 | Torben
Now I have two queries:
SELECT * From Names, Excludes
WHERE Names.Name = Excludes.Name
GROUP BY Names.Name
which results in
id | Name | Surname | id | Name
--------------------------------
1 | Michael | Kane | 1 |Michael
2 | Torben | Dane | 2 |Torben
Now i want to do the exact opposite with != to do the actual purpouse and erase the lines which have the names Michael and Torben in it
The seconds query is:
SELECT * From Names, Excludes
WHERE Names.Name != Excludes.Name
GROUP BY Names.Name
The Result is
id | Name | Surname | id | Name
--------------------------------
3 | Dinge | Chain | 2 |Torben
4 | Django | Fain | 2 |Torben
5 | Juliett | Bravo | 2 |Torben
1 | Michael | Kane | 2 |Torben
2 | Torben | Dane | 1 |Michael
The Result I would want to have is
id| Name | Surname
---------------------
3 | Dinge | Chain
4 | Django | Fain
5 | Juliett | Bravo
What am I doing wrong?
If you are going to use a join (as opposed to not in or not exists), you want a left join and where clause:
SELECT n.*
From Names n LEFT JOIN
Excludes e
ON n.Name = e.Name
WHERE e.Name IS NULL;
A simple rule: Never use commas in the FROM clause.
select * from Names
where Name not in (select Name from Excludes)
Use a left join instead and filter out null values:
SELECT Names.*
FROM Names
LEFT JOIN Excludes ON Names.Name = Excludes.Name
WHERE Excludes.Name IS NULL
The group by clause seemed meaningless so I removed it.
Another option is to use the not exists predicate with a correlated subquery:
SELECT * FROM Names n
WHERE NOT EXISTS
(SELECT Name FROM Excludes e WHERE e.Name = n.Name)
You can simply use (not in) with a inner query on Excludes ,
select * from Names where Name not in (select Name from Excludes)

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 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