SQL: Inner join help for assessment - mysql

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.

Related

How to make a certain set of values available in just one row

I would like to set up a database for a example project I have. I have chosen a driving school and work with tables including teachers, pupils, cars, time_lessons and bookings. The code can be seen in the code section. The four first tables i.e. teachers, pupils, cars and time_lessons are all foreign keys in bookings.
My issue is that I need certian combinations of these foreign keys to be available once. Let me clarify:
- A teacher cannot teach a same time_lessons twice
- A pupil cannot drive in a driving time_lessons twice
- A car cannot be used during the same time_lessons twice
- However a time in time_lessons can be used multiple times providing you have a pupil, a teacher and a car that haven't already been booked in that time.
How do I set this up?
See code below to see what I've done so far...
DROP DATABASE IF EXISTS c4trafik;
CREATE DATABASE c4trafik;
USE c4trafik;
CREATE TABLE teachers(
id INT PRIMARY KEY AUTO_INCREMENT,
t_fname VARCHAR(20) NOT NULL,
t_lname VARCHAR(20) NOT NULL,
auth_lvl INT DEFAULT 3
);
CREATE TABLE pupils(
id INT PRIMARY KEY AUTO_INCREMENT,
p_fname VARCHAR(20) NOT NULL,
p_lname VARCHAR(20) NOT NULL,
persnr CHAR(10) NOT NULL,
email VARCHAR(50) DEFAULT NULL,
telnr VARCHAR(10) DEFAULT NULL,
to_pay INT DEFAULT 0,
has_pay INT DEFAULT 0
);
CREATE TABLE cars(
id INT PRIMARY KEY AUTO_INCREMENT,
regnr VARCHAR(6) NOT NULL,
auto_gear BOOLEAN DEFAULT false
);
CREATE TABLE time_lessons(
id INT PRIMARY KEY AUTO_INCREMENT,
t_start TIMESTAMP NOT NULL,
t_end TIMESTAMP NOT NULL,
les_type VARCHAR(20) DEFAULT 'Driving Lesson'
);
CREATE TABLE bookings(
teachers_id INTEGER NOT NULL,
pupils_id INTEGER NOT NULL,
cars_id INTEGER NOT NULL,
time_lessons_id INTEGER NOT NULL,
FOREIGN KEY(teachers_id) REFERENCES teachers(id),
FOREIGN KEY(pupils_id) REFERENCES pupils(id) ON DELETE CASCADE,
FOREIGN KEY(cars_id) REFERENCES cars(id),
FOREIGN KEY(time_lessons_id) REFERENCES time_lessons(id) ON DELETE CASCADE,
PRIMARY KEY(teachers_id, pupils_id, cars_id, time_lessons_id)
);
INSERT INTO teachers (t_fname, t_lname, auth_lvl) VALUES
('Ali', 'Alisson', 1),
('Adam', 'Adamsson', 2),
('Noah', 'Noahsson', 3);
INSERT INTO pupils(p_fname, p_lname, persnr, email, telnr, to_pay, has_pay) VALUES
('Jakob', 'Jaboksson', '8702111254', 'pupil1#gmail.com','0704585962', 2800, 2800),
('Hassan','Hassansson', '9504234858' ,NULL,'0704125463',5000,1000),
('Mona','Monasson', '9410118547',NULL, NULL, 10200, NULL);
INSERT INTO cars (regnr, auto_gear) VALUES
('MNS111', false),
('OJS111', true),
('MNF111', false);
INSERT INTO time_lessons (t_start, t_end ) VALUES
('2019-06-10 08:00:00', '2019-06-10 08:40:00'),
('2019-06-10 08:50:00', '2019-06-10 09:30:00'),
('2019-06-10 09:40:00', '2019-06-10 10:20:00'),
('2019-06-10 10:30:00', '2019-06-10 11:10:00'),
('2019-06-10 11:20:00', '2019-06-10 12:00:00'),
('2019-06-10 13:10:00', '2019-06-10 13:50:00'),
('2019-06-10 14:00:00', '2019-06-10 14:40:00'),
('2019-06-10 14:50:00', '2019-06-10 15:30:00'),
('2019-06-10 15:40:00', '2019-06-10 16:20:00');
INSERT INTO bookings(teachers_id ,pupils_id, cars_id, time_lessons_id) VALUES
(1,2,1,1),(2,1,1,1),
(1,2,1,2),(2,1,2,2),
(1,1,1,3),(2,2,1,3);
-- Ska ge error om man duplicerar!!
-- Find main admin
SELECT t_fname, t_lname FROM teachers WHERE auth_lvl = 1;
-- How many manual geared cars?
SELECT COUNT(*) AS manually_geared FROM cars
WHERE auto_gear = false;
-- all booked student
SELECT p_fname, p_lname FROM bookings
INNER JOIN pupils ON pupils.id = bookings.pupils_id
GROUP BY p_lname, p_fname;
-- What time will each student drive?
SELECT p_fname, p_lname, t_start, t_end FROM bookings
INNER JOIN pupils ON pupils.id = bookings.pupils_id
INNER JOIN time_lessons ON time_lessons.id = bookings.time_lessons_id
ORDER BY p_lname, p_fname, t_start;
-- All time_lessons for teacher number 1
SELECT t_fname, p_fname, p_lname, t_start, t_end FROM bookings
INNER JOIN teachers ON teachers.id = bookings.teachers_id
INNER JOIN pupils ON pupils.id = bookings.pupils_id
INNER JOIN time_lessons ON time_lessons.id = bookings.time_lessons_id
WHERE teachers.id = 1;
So what I'm wondering is do I change my structure? Am I missing some code to make what I mentioned above work.
A teacher cannot teach a same time_lessons twice
Add a UNIQUE constraint on the (teacher_id,time_lesson_id) tuple in the booking, e.g.
CREATE UNIQUE INDEX booking_UX1 ON booking (teacher_id,time_lesson_id)
This will disallow rows with duplicates of a combination of values. If an attempt to INSERT a new row (or UPDATE and existing row) would cause two rows to have the same values, the statement will fail with error 1062 "Duplicate entry '' for key ".
A pupil cannot drive in a driving time_lessons twice
A UNIQUE index will prevent duplicates:
CREATE UNIQUE INDEX booking_UX2 ON booking (pupil_id,time_lesson_id)
(My preference is for entity tables be named in the singular, to name what one row represents.)

mysql subquery COUNT with WHERE clause confusion

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

how to use left join to output results from a column that are null using two different data types?

hi im having trouble doing a left join that outputs all customers who haven't made an order yet. im getting a conversion error and have tried using cast to convert it but it doesnt work. any help would be appreciated
here are my tables and data:`
create table Customers(
Cust_code varchar(2) primary key ,
[first_name] varchar(30) not null,
last_name varchar(30) not null,
address varchar(100) not null,
city varchar(35) not null)
create table Cust_Order(
order_no int ,
Cust_code varchar(2) not null,
order_date date not null,
meth_pmt varchar(30) not null,
constraint Ord_order_no_pk primary key(order_no),
constraint Cus_cust_code_fk foreign key(Cust_code) references Customers)
create table Product(
product_id int ,
product_name varchar(100) not null,
product_price decimal(8, 2) not null,
constraint Pro_product_id_pk primary key(product_id))
create table Order_Line(
order_no int ,
product_id int ,
qty int not null,
sale_price decimal(8,2) not null,
constraint Ord_order_no_product_id_pk primary key(order_no, product_id),
constraint Pro_product_id_fk foreign key(product_id) references Product)
insert into Customers (Cust_code, [first_name], last_name, address, city)
values('A1', 'Kath', 'Morgan','122 Lilain Street', 'Palmerston North'),
('A2','Mike','Smith','67 Golf Hill Drive','Wellington'),
('A3','Glen','Hoddle','San Quentin Ave','Palmerston North'),
('A4','Dan','Boone','Alamo Road','Wellington')
insert into Cust_Order (order_no, Cust_code, order_date, meth_payment)
values(1, 'A1','2014-01-16','CC'),
(2, 'A1','2014-02-16','CC'),
(3, 'A2','2014-01-16','CHEQUE'),
(4, 'A3','2014-03-17','CC')
insert into Product (product_id, product_name, product_price)
values(1, 'Network Card', 58.00),
(2, 'Motherboard', 150.00),
(3, 'Video Card', 232.00)
insert into Order_Line (order_no, product_id, qty, sale_price)
values(1,1,3,70.00),
(1,2,1,170.00),
(1,3,2,300.00),
(2,1,2,70.00),
(2,3,1,300.00),
(3,1,2,70.00),
(4,1,3,70.00)
my select statement using left join which should output the one customer who hasn't made an order
select first_name + last_name as 'Customers who have not made an order',
order_no from Customers as c
left join Cust_Order as o
on c.Cust_code = o.order_no
where order_no is null
Your join condition needs to match on the customer codes. Other than this, the logic of your query is correct, but MySQL doesn't use + for string concatenation, this is SQL Server syntax. Use the CONCAT function instead:
SELECT
CONCAT(c.first_name, ' ', c.last_name) AS `Customers who have not made an order`
FROM Customers AS c
LEFT JOIN Cust_Order AS o
ON c.Cust_code = o.Cust_code
WHERE o.Cust_code IS NULL;
The ANSI concatenation operator is ||, and you may also use it in MySQL if you set the appropriate mode:
SET sql_mode = PIPES_AS_CONCAT
If you must use a LEFT JOIN, try:
SELECT
CONCAT(C.first_name, ' ', C.last_name) AS 'Customers who have not made an order'
FROM Customers C LEFT JOIN Cust_Order CO
ON C.Cust_code=CO.Cust_code
GROUP BY Customer_FullName
HAVING COUNT(B.*)=0;
Or you can make use of EXISTS.
SELECT
CONCAT(C.first_name, ' ', C.last_name) AS 'Customers who have not made an order'
FROM Customers C
WHERE NOT EXISTS (SELECT NULL FROM Cust_Order CO WHERE C.Cust_code=CO.Cust_code);
See MySQL join made easy for insights on using joins.

what's the correct SQL query

The script for mysql database is as follow:
Create Table Aircraft
(
aid integer not null,
aname nchar(30) not null,
cruisingrange integer not null,
primary key (aid)
);
Create Table Flights
(
flNo integer not null,
flFrom nchar(20)not null,
flTo nchar(20) not null,
distance integer not null,
departs time not null,
arrives time not null,
price Decimal(6,2) not null,
primary key(flNo)
);
create table Pilots
(
pid integer not null,
pname nchar(20) not null,
salary Decimal(6,2) not null,
primary key(pid)
);
create table certified
(
pid integer not null,
aid integer not null,
primary key(pid,aid),
foreign key(pid) references Pilots(pid) ON DELETE CASCADE,
foreign key(aid) references Aircraft(aid) ON DELETE CASCADE
);
INSERT INTO Aircraft(aid,aname,cruisingrange)
VALUES
(1, 'B-450',10000),
(2, 'C-190',4000),
(3, 'RN-110',5000),
(4, 'kp-30',2000),
(5, 'sh-60',1500),
(6, 'mr-70',7000),
(7, 'VK-20',3500);
INSERT INTO Flights(flNo,flFrom,flTo,distance,departs,arrives,price)
VALUES
(100,'city1','city2',1200,'16:00:00','16:30:00',130),
(110,'city3','city4',1000,'18:00:00','19:00:00',160),
(112,'city5','city6',2000,'15:00:00','16:00:00',185),
(115,'city7','city8',4000,'14:00:00','16:00:00',250),
(118,'city9','city3',1500,'18:00:00','19:00:00',220),
(119,'city2','city3',2500,'20:00:00','21:30:00',180);
INSERT INTO Pilots(pid,pname,salary)
VALUES
(400,'jack',150),
(410,'pit',180),
(420,'nami',200),
(430,'rafel',110),
(440,'linda',300);
INSERT INTO Certified(pid,aid)
VALUES
(400,1),
(400,6),
(410,1),
(420,1),
(420,3),
(420,7),
(440,4),
(440,6);
I want a query to find the name of Aircrafts that ALL of Pilots certified for them have a salary more than 187$. In fact, that "ALL" is my problem! could anyone please help me?
You could rephrase the problem as finding the planes whose certified pilots' minimum salary is at least $187.
This is simple to convert to SQL:
SELECT aid
FROM ... all your joins here ...
GROUP BY aid
HAVING MIN(salary) >= 187
I would start with a pre-query of just those pilots who have the $187 you are looking for, then find the planes they are certified with...
select
QualifiedPilots.*,
A.AName,
A.CruisingRange
from
( select
P.*
from
Pilots P
where
P.Salary >= 187 ) as QualifiedPilots
JOIN Certified C
on QualifiedPilots.pid = C.pid
JOIN Aircraft A
on C.aid = A.aid
With the question and your comment as vague on what you wanted OUT, I've revised to include a group_concat on a per-pilot basis.
select
QualifiedPilots.*,
group_concat( A.AName ) CertAircraft
from
( select
P.*
from
Pilots P
where
P.Salary >= 187 ) as QualifiedPilots
JOIN Certified C
on QualifiedPilots.pid = C.pid
JOIN Aircraft A
on C.aid = A.aid
group by
QualifiedPilots.pid
This result shows only 2 pilots... showing pilot "nami" certified with 3 aircraft, and "linda" certified with 2 aircraft... total of 5 qualified certifications which is what the first query returns... just has the same name multiple times, but shows the aircraft detail.

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.