COUNT using two (or more) columns - mysql

Is it possible to perform a count based on a combination of two columns?
I have 3 tables:
student:
Student id | Student name | Gender | Dob |
class register: Student id | Class id
Both columns make up the composite primary key.
Classes: Class id | Class name | Class day
I would like to perform a count of the number of students of each gender in each class.
I came up with this query so far:
SELECT class_name AS 'class Name',
Child_gender AS 'Gender',
COUNT(student_id) AS 'Count'
FROM student,classRegister,classes
WHERE student.student_id=classRegister.student_id AND
classRegister.class_id=classes.class_id
GROUP BY class_name,student_gender
ORDER BY class_name;
But it seems to be giving me duplicate results for some of the students.
So, can I modify this query to count the number of instances of a combination of the student_id and class_id?
I'm still very new to MySQL, so I apologise if this is basic for everyone

Try this:
SELECT c.class_name AS 'class Name', s.Child_gender AS 'Gender',
COUNT(s.student_id) AS 'Count'
FROM student s
INNER JOIN classRegister cr ON s.student_id = cr.student_id
INNER JOIN classes c ON cr.class_id = c.class_id
GROUP BY s.Child_gender, s.class_id, c.class_name
ORDER BY c.class_name

SELECT c.class_name AS 'class Name', s.Child_gender AS 'Gender',
COUNT(DISTINCT s.student_id) AS 'Count'
FROM student s
INNER JOIN classRegister cr ON s.student_id = cr.student_id
INNER JOIN classes c ON cr.class_id = c.class_id
GROUP BY c.class_name, s.Child_gender
You might be getting the same student multiple times if they have multiple registrations in classRegister. That sounds gross. The DISTINCT in the COUNT should make sure that they are counted only once per class.

Related

SQL -- Finding students taking 2 or more classes

I had this question in an interview, and I couldn't quite figure it out. I have three tables: a student table, a classes table and a table linking the other two. Here is a basic schema for them.
STUDENT
student_id | student_name
-----------+-------------
int | varchar(30)
CLASS
class_id | class_name
---------+-----------
int | varchar(30)
ROSTERS
student_id | class_id
-----------+---------
int | int
The rosters table shows which students are taking which classes. How do I write a query showing which students are taking 2 or more classes?
This query will do what you want:
SELECT s.student_name, COUNT(DISTINCT r.class_id) AS num_classes
FROM student s
LEFT JOIN rosters r ON r.student_id = s.student_id
GROUP BY s.student_id
HAVING num_classes >= 2
It counts all the distinct class_id values in roster for each student (num_classes) and returns only students with 2 or more (HAVING num_classes >= 2).
Note I've used a LEFT JOIN to catch all students, however since you want only those with more than 1 class this is not necessary and you could use a straight JOIN.
Also note that it's not necessary to JOIN the class table for this question, however if you wanted the names of the classes the student was taking you would need to.
You can use join, count, having and group by to get the required output:
select r.student_id,s.student_name,count(*)
from STUDENT s inner join ROSTERS r
on s.student_id = r.student_id
group by r.student_id,s.student_name
having count(*)>=2;
You need to join the two tables, STUDENT and ROSTERS (I have used inner join, if required, this can be changed as per requirements), counting the number of classes each student is taking.
SELECT s.student_name,
COUNT(r.class_id) AS count
FROM
STUDENT s
INNER JOIN
ROSTERS r
ON
r.student_id = s.student_id
GROUP BY
s.student_id
HAVING
count >= 2
this will work:
select s.*,r.*,c.*,count(*)
from
student s,
class c,
rosters r where
s.student_id=r.student_id and
c.class_id=r.class_id
group by s.student_id
having count(*)>=2
SELECT COUNT(class_id), student_id
FROM Rosters
GROUP BY student_id
HAVING COUNT(class_id) >=2
This is the simplest way to do it IMO.

SQL Query, Getting multiple dates for one entity

I am working on writing a SQL query to produce a table that will look something like this:
Name |Dates Absent|Total Absences
student |10/28/2018 | 2
|10/29/2018 |
I currently have a data base which has 2 tables that are part of a larger system which contain the needed data (absences, students).
I have tried the following query
SELECT s.student_id,s.last_name,s.first_name, COUNT(s.student_id) AS 'Total Absences'
FROM `students` s, `absences` a INNER JOIN students ON students.student_id=a.student_id
Which yielded the following results:
student_id | last_name | first_name | Total Absences
1 | student | name | 12464
I want this to only use each ID once and count the times it appears. Is the problem from a relationship in the database that has many dates the one person can be absent? The ID was left in the select for now for debugging purposes, it will be removed later.
EDIT
I now have the query
SELECT s.last_name, s.first_name,a.date_absence, COUNT(s.student_id) AS 'Total Absences'
FROM `students` s, `absences` a
INNER JOIN students ON students.student_id=a.student_id
GROUP BY s.student_ID
This only displays one of the dates, how I can add all of the dates without redisplaying the students information?
You can do this with group_concat. It's not quite what you descibe, but it's close.
SELECT s.student_id,s.last_name,s.first_name, group_concat(a.date_absent) AS 'Dates Absent', COUNT(a.id) AS 'Total Absences'
FROM `students` s JOIN `absences` a ON s.student_id = a.student_id
GROUP BY s.student_id
which should yield
student_id | last_name | first_name | Dates Absent | Total Absences
1 | student | name | 10/28/2018,10/29/2018 | 2
It looks like you are almost there with the counting, but missing your GROUP BY statement
If you include aggregate functions, such as COUNT(), but leave off the GROUP BY, the whole intermediate result is taken as one group
You also seem to have a strange CROSS JOIN going on with your duplicate mention of the students table
If you want the absence dates in each row you'll have to use another aggregate function, GROUP_CONCAT()
Something along the lines of
SELECT s.student_id, /** Include as names could feasibly be duplicated */
CONCAT(s.first_name, ' ', s.last_name) name,
GROUP_CONCAT([DISTINCT] a.date) dates_absent, /** May want distinct here if more than one absence is possible per diem */
COUNT(*) total_absences
FROM students s
JOIN absences a
ON a.student_id = s.student_id
GROUP BY s.student_id[, name] /** name required for SQL standard */
[ORDER BY name [ASC]] /** You'll probably want some kind of ordering */
[] indicate optional inclusions

Return all courses a student is taking (Many to Many SQL Database example)

I'm fairly new to MySQL, and trying to understand the many-to-many relationship since these examples can popup in interviews
There are 3 tables, and since a Student can have many courses and a Course can have many students, this is a Many-to-Many relationship right?
The tables are
Student- has student ID, name, date of birth, and department.
Courses- Has ID, Name of course
Student_Courses- Has student_id, course_id
How would I display these 2 questions-
1) Given a studentID, return all the names of the courses the student is taking
2) Return the name of students who is taking X amount of courses or more (Ex. 4 or more courses).
Im trying to write queries on these, but I'm stuck...
In the case of selecting all of the courses for a given student ID you could try the following, which will return one row for each Course a Student is associated with.
select
s.name as StudentName,
c.name as CourseName
from `Student` as s
inner join `Student_Course` as sc on (sc.student_id = s.ID)
inner join `Course` as c on (c.ID = sc.course_id)
where
(s.`ID` = 'given_Student_ID_here')
;
As for selecting a list of the names of Students taking N or more courses, for this you might use an aggregating sub-select as a WHERE clause in which we reference one of the outer tables (i.e. [Student]) so that the result of the aggregation is personalised per Student record:
select
s.name as StudentName
from `Student` as s
where
(
(
select count(*)
from `Student_Course` as sc
inner join `Course` as c on (c.ID = sc.course_id)
where (sc.student_id = s.ID)
) >= 4
)
;
You might also consider an alternative approach to this second problem by using the GROUP BY and HAVING clauses:
select
s.name as StudentName
from `Student` as s
inner join `Student_Course` as sc on (sc.student_id = s.ID)
inner join `Course` as c on (c.ID = sc.course_id)
group by
s.name
having
count(*) >= 4
;

Group SQL by newly defined parameters

i have gotten the task of creating a statistic from tables that look like this:
Faculty
1 FacultyName1
2 FacultyName2
3 FacultyName3
4 FacultyName4
5 FacultyName5
and this:
Student
1 StudentName1 FacultyNr2
2 StudentName2 FacultyNr3
3 StudentName3 FacultyNr5
4 StudentName4 FacultyNr2
now i have to create a statistic which Groups the Faculties into newly created fields and groups by them.
Say:
Faculty Group 1 Count: 3
Faculty Group 2 Count: 1
for this example lets say that all those of FacultyName1,FacultyName2,FacultyName3 should be listet as of "Faculty Group 1" and FacultyName4 and FacultyName5 as of "Faculty Group 2".
I started by doing the following:
Select Count(*)
FROM Student INNER JOIN Faculty on Student.FacultyID = Faculty.ID
But am stuck trying to understand how to Group, how i could create Groups in the Code, where i could just say: Group by FacultyGroups (Select Case When FacultyName = 'FacultyName1' = 'Faculty Group 1')
or something similiar, does anybody have any idea ?
Assuming that you have added a GroupID column in your Faculty table
SELECT COUNT(*), f.GroupID
FROM Student AS s
INNER JOIN Faculty AS f ON s.FacultyID = f.ID
GROUP BY f.GroupID
It gives you the number of student per group of faculties and the id of this group
There are better ways, but this should work:
SELECT
CASE
WHEN f.Name IN ('FacultyName1', 'FacultyName2', 'FacultyName3') THEN 'FacultyGroup1'
WHEN f.Name IN ('FacultyName4', 'FacultyName5') THEN 'FacultyGroup2'
END AS FacultyGroup,
COUNT(*) AS Students
FROM
Student s
INNER JOIN Faculty f ON s.FacultyID = f.ID
GROUP BY
CASE
WHEN f.Name IN ('FacultyName1', 'FacultyName2', 'FacultyName3') THEN 'FacultyGroup1'
WHEN f.Name IN ('FacultyName4', 'FacultyName5') THEN 'FacultyGroup2'
END;
If your "group" logic becomes too long then it will look messy in your query, so you might want to pre-calculate this. You could do this by using a sub-query for example, so one part of your query (the sub query) would convert faculties to groups and the other "main" part would count the students per group.

SQL statement that shows at least 16 courses not taken

I have four schemas:
takes(ID,course_id,sec_id,semester,year)
student(ID,name,dept_name,tot_credit)
course(course_id,title,dept_name,credits)
department(dept_name,building,budget)
I want to create a query that finds the name and id of each Astronomy student whose name begins with the letter ’T’ and who has not taken at least 16 Astronomy courses.
What's the easiest way I could do this?
I already wrote this beginning bit
SELECT name, id
FROM student
WHERE dept_name='Astronomy' AND name LIKE '%T%'
I'm not quite sure how to finish this off.
Any help would be greatly appreciated :)
Here's the result
NAME ID CLASS_TAKEN
-------------------- ----- -----------
Tolle 38279 12
Teo 62268 13
Tolle 93223 13
Tsukamoto 17707 5
Titi 11576 9
Teo 91772 12
Toraichi 50387 11
Tewari 80754 14
Tiroz 64091 14
9 rows selected
I need Teo with the id 91772 and Tewari 80754 to be gone
Given my reading of the requirements and the comments it's pretty clear that the question is not very clear. :-) What you're looking for are students where (total # of courses given by the Astronomy department) - (# of Astronomy courses taken by student) >= 16. So, how do we find these values? First, let's start with the total number of courses given by the Astronomy department. This is pretty simple:
SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'
Now, the second part is to determine how many courses given by the Astronomy department each student has taken. To do this we need to start with the student, join to the courses the student has taken (the TAKES table), then join to the COURSES table to find out which department each course is part of. Something like the following should do it:
SELECT s.ID, s.NAME, COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY s.ID, s.NAME;
OK, now we need to put this together. You've tagged this question for both Oracle and MySQL so I'm going to guess you'll accept valid syntax for either database; thus I'll use Oracle Common Table Expression syntax to pull everything together:
WITH ASTRONOMY_COURSES AS (SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'),
STUDENT_ASTRO_COURSES AS (SELECT s.ID,
s.NAME,
COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY ID)
SELECT s.ID,
s.NAME,
s.STUDENT_ASTRO_COUNT,
a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT AS UNTAKEN_COUNT
FROM STUDENT_ASTRO_COURSES s
CROSS JOIN ASTRONOMY_COURSES a
WHERE a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT >= 16;
Note here that a CROSS JOIN is used to put together the subqueries. This means that all the rows of each subquery are joined to all the rows of the other subquery - but since in this case the ASTRONOMY_COURSES subquery will only return a single row what we're doing is appending the ASTRONOMY_COURSE_COUNT value onto each row returned by the STUDENT_ASTRO_COURSES subquery.
That should at least get you pretty close. Amend as needed.
Not tested on animals - you'll be first! :-)
Share and enjoy.
Do you need to use all tables?
Table department has no links with the student,
Table takes has no links with the student,
Table coursehas no links with the student.
If student lists total credits that are all Astronomy I think this can be used:
select name, id, MAX(tot_credit) as credits
from student
where dept_name='Astronomy' and name like 'T%'
group by name, id
having MAX(tot_credit)<=16
PS - you schema is not good; PK-FK references are missing
Your query will need to reference more tables than just the student table.
Your tables seem be missing some important information, which student has taken which course. There's a table named takes, but there doesn't appear to be any relationship between takes and student.
So first, figure out how to list the students along with the Astronomy courses they have taken. Each row will identify the student and a course.
SELECT s.id AS student_id
, s.name AS student_name
, t.???
FROM student s
JOIN ??? t
ON t.student_id = s.id
WHERE ...
You may also need to include another "join" to an additional table, in order to identify which course a student has taken is an Astronomy course.
To also include students that have not take any Astronomy courses, you can use an outer join, rather than an inner join. (That would mean including the LEFT keyword before JOIN, and relocating predicates from the WHERE clause to the ON clause. (A predicate in the WHERE clause that can only be satisfied by non-NULL values will negate the outer-ness of the join.)
Once you have a query that returns that set (students along with any astronomy courses they've taken), you can then add a GROUP BY clause to "collapse" a set of rows into a single row. (Looks like you want the rows "grouped" by student.)
And then an aggregate function like COUNT() or SUM() can be used to get a count of rows for each group. (If you don't want to count any re-takes of a course (a "duplicate" course for a student) you may be able to make use of the COUNT(DISTINCT t.foo) form.
And then a HAVING clause can be added to the query, to compare the value returned from the aggregate expression to a constant value, to return only rows that satisfy a specific condition.
FOLLOWUP
Assuming:
CREATE TABLE course
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, title VARCHAR(30) NOT NULL COMMENT 'course title'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, credits DECIMAL(5,2) COMMENT 'credit hours'
, PRIMARY KEY (id)
);
CREATE TABLE student
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, name VARCHAR(30) NOT NULL COMMENT 'student name'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, tot_credit INT COMMENT '?'
, PRIMARY KEY (id)
);
CREATE TABLE takes
( student_id INT UNSIGNED NOT NULL COMMENT 'FK ref student.id'
, course_id INT UNSIGNED NOT NULL COMMENT 'FK ref course.id'
, sec_id INT UNSIGNED NOT NULL COMMENT '?'
, semester INT UNSIGNED NOT NULL COMMENT '?'
, year INT UNSIGNED NOT NULL COMMENT '?'
, PRIMARY KEY (student_id, course_id, sec_id, semester, year)
, CONSTRAINT FK_takes_course FOREIGN KEY (course_id) REFERENCES course (id)
, CONSTRAINT FK_takes_student FOREIGN KEY (student_id) REFERENCES student (id)
);
Query to get a list of students...
SELECT s.id
, s.name
FROM student s
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Get list of students along with the courses they've taken, returning the id of the ASTRONOMY courses they've taken...
SELECT s.id AS student_id
, s.name AS student_name
, c.id AS course_id
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Collapse the rows to one per student using a GROUP BY, and use aggregate functions to get counts or totals.
SELECT s.id AS student_id
, s.name AS student_name
, SUM(c.credits) AS total_astronomy_credits_taken
, COUNT(c.id) AS count_astronomy_courses_taken
, COUNT(DISTINCT c.id) AS count_distinct_astronomy_courses_taken
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
GROUP
BY s.id
, s.name
To omit rows from this resultset, add a HAVING clause. For example, to exclude rows where total_astronomy_credits_taken is greater than or equal to 12...
HAVING total_astronomy_credits_taken >= 12
If you want the rows returned in a certain sequence, specify that in an ORDER BY clause
ORDER BY s.id
If you want to replace NULL values from the aggregates with zeroes, you can warp the aggregate expression in an IFNULL(foo,0) function, e.g.
, IFNULL(COUNT(c.id),0) AS count_astronomy_courses_taken
Try this :
select a.name, a.id, count(b.ID) as class_taken
from student a inner join takes b
on a.ID = b.ID
inner join course c
on b.course_id = c.course_id
where a.dept_name='Astronomy' and substring(a.name,1,1) = 'T'
group by a.name, a.id
having count(b.ID) < 17