mysql subquery COUNT with WHERE clause confusion - mysql

I been mashing buttons all day but cant get this query to work. I have 3 tables students, courses and enrollment table that shows which classes the students have enrolled in
The query needs to retrieve all courses having at least 2 students enrolled which is ordered by course with the greatest number of students
I worked out how to retrieve the count of enrollments per class but having trouble filtering enrollments to >= 2 students
-- coursetable -----------------------------
CREATE TABLE StudentTable(
studentID VARCHAR(255) NOT NULL,
firstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255) NOT NULL,
DOB DATE NULL,
CONSTRAINT pk_studentTable PRIMARY KEY(studentID)
);
-- coursetable -----------------------
CREATE TABLE CourseTable(
courseID VARCHAR(255) NOT NULL,
courseName VARCHAR(255) NOT NULL,
hoursPerWeek int(11) NULL,
startDate DATE NULL,
CONSTRAINT pk_courseTable PRIMARY KEY(courseID)
);
-- enrolment table --
CREATE TABLE EnrolmentTable(
studentID VARCHAR(255) NOT NULL,
CourseID VARCHAR(255) NOT NULL,
CONSTRAINT pk_enrolmentTable PRIMARY KEY(studentID, CourseID)
);
this is the query i can do showing enrollments of all classes but it shows one class having only 1 student enrolled. I need it to only display classes with => 2 enrollments
SELECT ct.CourseName AS Course_Name, COUNT(st.studentID) AS Students_Enrolled
FROM EnrolmentTable et
INNER JOIN courseTable ct ON ct.courseID = et.courseID
INNER JOIN studentTable st ON st.studentID = et.studentID
GROUP BY et.courseID;
I need to use a subquery right? but not sure how

You can use HAVING to filter the result
SELECT ct.CourseName AS Course_Name, COUNT(st.studentID) AS Students_Enrolled
FROM EnrolmentTable et
INNER JOIN courseTable ct ON ct.courseID = et.courseID
INNER JOIN studentTable st ON st.studentID = et.studentID
GROUP BY et.courseID
HAVING Students_Enrolled> 1
ORDER BY Students_Enrolled DESC

Related

MySQL query with specific constraints?

I'm trying to create MySQL queries to answer some questions after creating the following database:
create table teachers (
teacher_id int primary key auto_increment,
name varchar(50),
surname varchar(50),
dob date,
employment_day date
);
create table students (
student_id int primary key auto_increment,
name varchar(50),
surname varchar(50),
dob date,
no_of_lessons_left int
);
create table aircrafts (
aircraft_id int primary key auto_increment,
manufacturer varchar(50),
serial_number varchar(50), -- or int?
cycles int
);
create table schools (
school_id int primary key auto_increment,
address varchar(50),
opening_date date
);
create table lessons (
lesson_id int primary key auto_increment,
teacher_id int not null,
student_id int not null,
aircraft_id int not null,
school_id int not null,
lesson_date date,
constraint lessons_teacher foreign key(teacher_id) references teachers(teacher_id),
constraint lessons_student foreign key(student_id) references students(student_id),
constraint lessons_aircraft foreign key(aircraft_id) references aircrafts(aircraft_id),
constraint lessons_school foreign key(school_id) references schools(school_id)
);
Below some queries, I tried for the questions using double join:
Questions are :
• How many aircraft are there per each school at some point in time?
I can't figure out how to answer these conditions " per each school at some point of time"
• How many students are attending one particular school?
select count(*) from students s
inner join lessons l on l.student_id = s.student_id
inner join schools sc sc.teacher_id= l.teacher_id
where sc.name = "PilotingSchoolName";
• How many students does each teacher have at some particular moment
select count(*) from students s
inner join lessons l on l.student_id = s.student_id
inner join schools sc sc sc.teacher_id= l.teacher_id;
However, I'm missing the condition " at some particular moment " in the query I wrote.
You have a lessons.lesson_date column, and can limit to one point in time with something like AND lesson_date = '2019-12-08' or a date range with AND lesson_date BETWEEN '2018-01-01' AND '2019-12-08'. If the requirements did not ask you to find a specific exact date, it sounds like they'd just like you to be able to demonstrate how to use lesson_date in WHERE.
In your response to "how many students does each teacher have" problem, you are missing a crucial GROUP BY clause. The query you have now is close, but will instead show the total number of enrolled students. Grouping by teachers.name will complete the query (along with a lesson_date constraint)
select
t.name,
-- count each student only once per teacher
count(DISTINCT s.student_id) AS
from teachers t
-- left join so teachers with no enrolled
-- students will list as zero
left join lessons l on t.teacher_id = l.teacher_id
left join students s ON l.student_id = s.student_id
WHERE
-- A specific date
l.lesson_date = '2019-12-08'
GROUP BY t.name
To get the aircrafts per school at a particular time, the query is very similar, but with different joins.
select
-- List all schools, and count of aircraft per school
s.school_id,
count(DISTINCT l.aircraft_id)
from
schools s
-- left join, so that schools with zero current aircraft
-- are include in the results
left join lessons l ON l.school_id = s.school_id
where
l.lesson_date = '2019-12-08'
-- GROUP BY necessary to get the count per school
GROUP BY s.school_id

SQL getting empty fields from other table

i am working with views for the first time.
I can see the records from table Student in the view, but the records from table Course are not shown getting NULL everywhere.
Here are the tables I created:
-- Tabel StudentCourse maken
CREATE TABLE StudentCourse
(
StudentCourse int NOT NULL,
CourseId int NOT NULL,
StudentId int NOT NULL
)
-- DROP table StudentCourse
-- Tabel Course maken
CREATE TABLE Course
(
CourseId int NOT NULL,
Name varchar(255) NOT NULL,
StartDate date NOT NULL,
EndDate date NOT NULL,
Period int NOT NULL,
IsWeekEnd bit NOT NULL,
IsActive bit NOT NULL
)
-- DROP table Course
-- Tabel Student maken
CREATE TABLE Student
(
StudentId int NOT NULL,
Name varchar(255) NOT NULL,
Age int NOT NULL,
Address varchar(255) NOT NULL,
Registered datetime NOT NULL,
IsActive bit NOT NULL,
Description varchar(255) NOT NULL
)
-- DROP table Student
And here is the view i created
CREATE VIEW V AS (
SELECT s.StudentId,s.Name,c.CourseId,c.StartDate FROM
Student s
LEFT JOIN
Course c
ON
s.Name=c.Name
);
Thanks for your time!
There are no Courses with the same name as names from students. You need to LEFT JOIN Student on StudentCourse and INNER JOIN StudentCourse on Course.
SELECT s.StudentId,s.Name,c.CourseId,c.StartDate
FROM Student s
LEFT JOIN StudentCourse sc ON s.StudentId = sc.StudentId
INNER JOIN Course c ON sc.CourseId = c.CourseId

SQL - using tables to find out How many types of each book a publisher has

I have two tables, publishers and titles.
I want to find out how many types of each book the publisher has, such as history, childrens, etc.
Here are the two tables:
CREATE TABLE publishers
(
pub_id CHAR(3) NOT NULL,
pub_name VARCHAR(20) NOT NULL,
city VARCHAR(15) NOT NULL,
state CHAR(2) ,
country VARCHAR(15) NOT NULL,
CONSTRAINT pk_publishers PRIMARY KEY (pub_id)
)ENGINE = InnoDB;
CREATE TABLE titles
(
title_id CHAR(3) NOT NULL,
title_name VARCHAR(40) NOT NULL,
type VARCHAR(10) ,
pub_id CHAR(3) NOT NULL,
pages INTEGER ,
price DECIMAL(5,2) ,
sales INTEGER ,
pubdate DATE ,
contract SMALLINT NOT NULL,
CONSTRAINT pk_titles PRIMARY KEY (title_id)
)ENGINE = InnoDB;
All I have been able to do so far is find out the total count of types(genres) of books.
SELECT COUNT(DISTINCT type)
FROM publishers AS a
INNER JOIN titles AS p
ON a.pub_id = p.pub_id;
How can I go about doing this?
You need to have a query with Group By as shown below
SELECT a.pub_id, a.pub_name, p.type, COUNT(DISTINCT p.type)
FROM publishers AS a
INNER JOIN titles AS p ON a.pub_id = p.pub_id
GROUP BY a.pub_id, a.pub_name, p.type
add group by for pub_id and pub_name to avoid two publishers having same name to be considered as one publisher
Try this out: http://sqlfiddle.com/#!9/9331e/24
SELECT p.pub_name, t.type, count(t.type)
FROM publishers AS p
INNER JOIN titles AS t on t.pub_id = p.pub_id
GROUP BY p.pub_id, t.type

SQL: Inner join help for assessment

On my controlled assessment in school I'm stuck on this question:
Create, run, test, explain and demonstrate scripts to do the following:
Produce a list of all entries with the OCR exam board, showing:
the names of the students with entries
the subject names and level of entry for the exams the students are entered for.
Produce a list of all students, showing the students’ names, followed by the exams to be
taken. This list should be presented in alphabetical order by the student’s last name.
In my code, I don't know how to join more than 2 tables with INNER JOIN, but if I try to the 'ON' statement doesn't want to work, I don't know how to solve this question.
CREATE tables and INSERT data:
CREATE TABLE IF NOT EXISTS students
(
student_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
first_name VARCHAR(20) NOT NULL,
middle_name VARCHAR(20),
last_name VARCHAR(40) NOT NULL,
email VARCHAR(60) NOT NULL,
password CHAR(40) NOT NULL,
reg_date DATETIME NOT NULL,
PRIMARY KEY (student_id),
UNIQUE (email)
);
INSERT INTO students (first_name,last_name,email,password,reg_date) VALUES
("ex1","ex1.1","example1#gmail.com","11062001",'2009-12-04 13:25:30'),
("ex2","ex2.2","example2#gmail.com","ex123",'2015-02-12 15:20:45'),
("my name is jeff","21","kid","mynameis21kid#vine.com","yolo",'2014-09-21 14:15:25'),
("Mr.Right","Mr.Calvin","Mr.Hildfiger","Mr.misters#mister.com","mistermaster",'2015-06-04 19:50:35'),
("Bob","Dabuilda","bobthebuilder#fixit.com","BTBCWFI?",'2005-11-12 21:20:55');
CREATE TABLE IF NOT EXISTS subjects
(
subject_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
subject_name VARCHAR(20) NOT NULL,
level_of_entry VARCHAR(5) NOT NULL,
exam_board VARCHAR(10) NOT NULL,
PRIMARY KEY (subject_id),
UNIQUE(subject_id)
);
INSERT INTO subjects (subject_name,level_of_entry,exam_board) VALUES
("Chemistry","AS","OCR"),
("Biology","GCSE","AQA"),
("Music","GCSE","Edexcel"),
("English","A","OCR"),
("Physics","A","AQA"),
("Computing","GCSE","Edexcel"),
("French","A","AQA"),
("Maths","AS","OCR"),
("Product Design","GCSE","AQA"),
("History","AS","OCR");
CREATE TABLE IF NOT EXISTS entries
(
entry_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
date_of_exam DATE NOT NULL,
student_id INT UNSIGNED NOT NULL,
subject_id INT UNSIGNED NOT NULL,
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (subject_id) REFERENCES subjects(subject_id),
PRIMARY KEY (entry_id)
);
INSERT INTO entries (date_of_exam, student_id, subject_id) VALUES
('2015-05-31', 1, 6),
('2015-05-31', 2, 10),
('2015-01-21', 3, 3),
('2015-01-21', 4, 7),
('2015-09-13', 5, 1),
('2015-09-13', 2, 9),
('2015-12-06', 4, 8),
('2015-12-06', 1, 2),
('2015-04-01', 3, 5),
('2015-04-01', 5, 4);
And the SELECT:
SELECT entries.*, subjects.subject_name, subjects.level_of_entry
FROM subjects
INNER JOIN entries,
students ON entries.subject_id = subjects.subject_id
WHERE subjects.exam_board LIKE "OCR%";
Provide the join condition right after the join to keep it simple and readable. As jarlh wrote, do not mix the explicit joins with the comma separated list. There is no need to do a like with pattern matching if you know the exact value you are looking for. Just use the = operator with the exact value.
SELECT *
FROM subjects
INNER JOIN entries ON entries.subject_id = subjects.subject_id --join 2 tables
INNER JOIN students ON entries.student_id=students.student_id --join the 3rd tables
WHERE subjects.exam_board = "OCR";
Use explicit JOIN's, start now and never look back! Also use aliases to keep things neat.
SELECT e.*, su.subject_name, su.level_of_entry
FROM subjects su
INNER JOIN entries e ON e.subject_id = su.subject_id
INNER JOIN students st ON e.student_id = st.student_id
WHERE su.exam_board LIKE "OCR%";
What I think you might need the code to look like is this:
select entries.*, subjects.subject_name, subjects.level_of_entry
from entries
join students on students.student_id = entries.student_id
join subjects on subjects.subject_id = entries.subject_id
where exam_board = 'OCR';
I hope this helps.

Why is this LEFT JOIN eliminating records with nothing in the other table?

I have a MySQL Left Join problem.
I have three tables which I'm trying to join.
A person table:
CREATE TABLE person (
id INT NOT NULL AUTO_INCREMENT,
type ENUM('student', 'staff', 'guardian') NOT NULL,
first_name CHAR(30) NOT NULL,
last_name CHAR(30) NOT NULL,
gender ENUM('m', 'f') NOT NULL,
dob VARCHAR(30) NOT NULL,
PRIMARY KEY (id)
);
A student table:
CREATE TABLE student (
id INT NOT NULL AUTO_INCREMENT,
person_id INT NOT NULL,
primary_guardian INT NOT NULL,
secondary_guardian INT,
join_date VARCHAR(30) NOT NULL,
status ENUM('current', 'graduated', 'expelled', 'other') NOT NULL,
tutor_group VARCHAR(30) NOT NULL,
year_group VARCHAR(30) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (person_id) REFERENCES person(id) ON DELETE CASCADE,
FOREIGN KEY (primary_guardian) REFERENCES guardian(id),
FOREIGN KEY (secondary_guardian) REFERENCES guardian(id),
FOREIGN KEY (tutor_group) REFERENCES tutor_group(name),
FOREIGN KEY (year_group) REFERENCES year_group(name)
);
And an incident table:
CREATE TABLE incident (
id INT NOT NULL AUTO_INCREMENT,
student INT NOT NULL,
staff INT NOT NULL,
guardian INT NOT NULL,
sent_home BOOLEAN NOT NULL,
illness_type VARCHAR(255) NOT NULL,
action_taken VARCHAR(255) NOT NULL,
incident_date DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (student) REFERENCES student(id),
FOREIGN KEY (staff) REFERENCES staff(id),
FOREIGN KEY (guardian) REFERENCES guardian(id)
);
What I'm trying to select is the first name, last name and the number of incidents for each student in year 9.
Here's my best attempt at the query:
SELECT p.first_name, p.last_name, COUNT(i.student)
FROM person p, student s LEFT JOIN incident i ON s.id = i.student
WHERE p.id = s.person_id AND s.year_group LIKE "%Year 9%";
However, it ignores any students without an incident which is not what I want - they should be displayed but with a count of 0. If I remove the left join and the count then I get all the students as I would expect.
I've probably misunderstood left join but I thought it was supposed to do, essentially what I'm trying to do?
Thanks for your help,
Adam
What you are doing is fine, you just missed off the group by clause
SELECT p.first_name, p.last_name, COUNT(i.student)
FROM person p, student s LEFT JOIN incident i ON s.id = i.student
WHERE p.id = s.person_id AND s.year_group LIKE "%Year 9%"
GROUP BY p.first_name, p.last_name;
Here's some test data
insert into person values(1, 'student', 'Alice', 'Foo', 'f','1970-01-01');
insert into person values(2, 'student', 'Bob', 'Bar', 'm','1970-01-01');
insert into student values(1,1,0,0,'', 'current','','Year 9');
insert into student values(2,2,0,0,'', 'current','','Year 9');
insert into incident values(1,1,0,0,0,'flu','chicken soup', '2008-01-08');
And here's the output of the query with the group by added to it:
+------------+-----------+------------------+
| first_name | last_name | COUNT(i.student) |
+------------+-----------+------------------+
| Alice | Foo | 1 |
| Bob | Bar | 0 |
+------------+-----------+------------------+
You could further clean up the query by making join clauses from your where clause, and grouping on the person id:
SELECT p.first_name, p.last_name, COUNT(i.student)
FROM person p
INNER JOIN student s ON(p.id = s.person_id)
LEFT JOIN incident i ON(s.id = i.student)
WHERE s.year_group LIKE "%Year 9%"
GROUP BY p.id;
Alternately, you could avoid the LEFT JOIN by using a correlated subquery:
SELECT
p.first_name
, p.last_name
, (SELECT COUNT(*) FROM incident i WHERE i.student = s.id)
FROM
person p JOIN student s on s.person_id = p.id
WHERE
s.year_group LIKE "%Year 9%"
You're using count without group by for a start, and you're mixing "where" and "on" syntax for joins.
Try this:
SELECT p.first_name, p.last_name, COUNT(i.student)
FROM person p
JOIN student s on p.id = s.person
LEFT JOIN incident i ON s.id = i.student
WHERE s.year_group LIKE "%Year 9%"
GROUP BY P.id;
Would that not be a left outer join you are looking for? I may have my terminology mixed up? Would not be the first time. But Aron's answer would work.