Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
33 views

Continue Learning SQL By Examples_ One Hundred Examples of SQL Queries, Stored Procedures, and Functions for MySQL

This document is a guide for learning SQL through practical examples, focusing on complex queries, functions, and database relationships. It covers topics such as creating tables, using aggregate functions, and implementing various SQL operations within a computer school database context. The book aims to enhance the reader's SQL skills by providing hands-on exercises and solutions.

Uploaded by

Gregory Hilaire
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views

Continue Learning SQL By Examples_ One Hundred Examples of SQL Queries, Stored Procedures, and Functions for MySQL

This document is a guide for learning SQL through practical examples, focusing on complex queries, functions, and database relationships. It covers topics such as creating tables, using aggregate functions, and implementing various SQL operations within a computer school database context. The book aims to enhance the reader's SQL skills by providing hands-on exercises and solutions.

Uploaded by

Gregory Hilaire
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 155

Continue

Learning SQL by Examples

SERGEY SKUDAEV
Copyright © 2025 Sergey Skudaev
All rights reserved.

Freepik https://freepik.com designs the image of databases on the book cover.

Table of Contents
Introduction
Simple Select Statement
Complex Select Statements
Aggregate Functions
Window Functions
The RANK and DENSE_RANK Functions
Conditional Logic with CASE
Stored Procedures
Stored Functions
Homework
Create Stored Procedures
Create Stored Functions
Homework Solutions
Queries From The Homework
Stored Procedures From The Homework
Functions From The Homework
Resources
Author's Books On Amazon.com
About The Author
INTRODUCTION
This book is for readers familiar with SQL and have read my previous book,
Learn SQL by Examples. In the new book, we will create more complex SQL
queries. For example, the queries that join more than three tables. You will learn
to use new functions, regular expressions, and more.
To refresh your memory, and for those who did not read my first book, I will
repeat the information about the database structure used in my previous book
and this book.
SQL stands for Structured Query Language. SQL is designed to manage data
in a relational database management system (RDBMS). The relational database is
based on the relational model. In the relational database, each record in one
table is linked to the corresponding record(s) in the other table(s). The records´
IDs are used to link related records
SQL is a standard way to manipulate relational database data, and any
computer language programmer should know SQL. Even though it is standard,
there are slightly different SQL "dialects" for various databases. This book
provides examples of SQL queries that I tested on MySQL.
A database is an application that stores data in organized data sets and manages
data storage, retrieval, and modification. In the relational database, data is stored
in tables. Each table holds many records. Each record contains many fields and
is identified with an ID that is the record's primary key. For example, this is a
computer courses table. The primary key is the courseid field.
In our tutorial, we will use a computer school database. Tables in the
database are linked. There are three types of relationships between related tables:
One to One,
One to Many,
Many to Many.
When one record in one table is related to only one record in another, it is a
“One to One” relationship. For example, one order record in the ORDER table
is related to one order details record in the OrderDetails table.
When one record in one table relates to many records in another, it is a
“ONE to Many” relationship. I created a ‘teachers’ table and a ‘cars’ table in our
school database. One teacher may have a few cars, but each car can belong to
only one teacher.
The ‘cars’ table has the following fields: cid, teacherid, cmake, and cyear.

CREATE TABLE cars (


cid int NOT NULL,
teacherid int DEFAULT NULL,
cmake varchar(20) DEFAULT NULL,
cmodel varchar(30) NOT NULL,
cyear varchar(4) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

The ‘cars’ table has a primary key cid and a foreign key teacherid. The
teacherid field links a ‘cars’ table to a ‘teachers’ table record.
The ‘teachers’ table has the following fields:
teacherid, lastname, firstname, email, phone, hiredate, trate.

CREATE TABLE teachers (


teacherid int NOT NULL,
lastname varchar(15) NOT NULL DEFAULT '',
firstname varchar(15) NOT NULL DEFAULT '',
email varchar(30) NOT NULL DEFAULT '',
phone varchar(20) NOT NULL DEFAULT '',
hiredate date DEFAULT NULL,
rate int NOT NULL DEFAULT '30',
age int DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

A student may take many computer courses, and many students can take
the same computer course. The relationship between students and courses is
MANY TO MANY. To implement a "MANY TO MANY" relationship, we
must create the student_course table. It will hold studentid and courseid as
foreign keys and link student records with course records.
The ‘students’ table has the following fields:
studentid, lastname, firstname, email, phone, age, gender, startdate.

CREATE TABLE students (


studentid int NOT NULL,
lastname varchar(15) NOT NULL DEFAULT '',
firstname varchar(15) NOT NULL DEFAULT '',
email varchar(3NOT NULL DEFAULT '',
phone varchar(3NOT NULL DEFAULT '',
age int NOT NULL,
dob date DEFAULT NULL,
gender varchar(6) NOT NULL DEFAULT '',
startdate date DEFAULT NULL,
enddate date DEFAULT NULL,
street varchar(100) NULL,
city varchar(50) DEFAULT NULL,
state varchar(2) DEFAULT NULL,
zip varchar(10) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

How do we enter the address for each student? Entering an address for each
student at a time will take time. Let’s do it at once. First, we can enter the
city, state, and zip code for all students. We can do it if all students live in
one city.

UPDATE students SET city='St. Petersburg', state='FL', zip='33711';

The students cannot live in one house, so the house number and street
must be unique for each student. We can achieve that using SQL
functions.

UPDATE students SET street=


CONCAT( FLOOR(10 + RAND() * 21), ' ',
FLOOR(1 + RAND() * 20), 'th Avenue');

Explanation:
The FLOOR function rounds a decimal number to the nearest lower
integer.
The RAND() function in SQL generates a random floating-point
number between 0 (inclusive) and 1 (exclusive).

The FLOOR(10 + RAND() * 21) expression generates a random house


number between 10 and 30.

The FLOOR(1 + RAND() * 20) expression generates a random street


number between 1 and 20.
The CONCAT function concatenates the house number, street number,
and street name into a single address string.

Let’s see what we get.

SELECT street, city, state, zip FROM students;

Output 1:
street city state zip
28 15th Avenue St.Petersburg FL 33711
28 7th Avenue St.Petersburg FL 33711
28 12th Avenue St.Petersburg FL 33711
13 4th Avenue St.Petersburg FL 33711
15 18th Avenue St.Petersburg FL 33711
20 19th Avenue St.Petersburg FL 33711
12 18th Avenue St.Petersburg FL 33711
30 7th Avenue St.Petersburg FL 33711
23 4th Avenue St.Petersburg FL 33711
200 West Ave N Clearwater FL 33587
100 West Ave N Clearwater FL 33587
106 Main street Dunedin FL 34697

How do you enter random birthdays for all students at once? Assuming
they were born about 18-25 years ago.
UPDATE students SET dob =
DATE_SUB(CURDATE(),
INTERVAL (18 + FLOOR(RAND() * 8)) YEAR;

Explanation:

The CURDATE() function gets the current date.


The RAND() function generates a random float value between 0 and 1.
The FLOOR(RAND() * 8) expression generates a random integer
between 0 and 7.
The (18 + FLOOR(RAND() * 8)) expression generates a random integer
between 18 and 25.
The DATE_SUB(CURDATE(), INTERVAL (18 + FLOOR(RAND() *
8)) YEAR) expression subtracts a random number of years (between 18
and 25) from the current date.
This query will update the birthdate field for all students with a random
date within the range of 18-25 years ago.

Let’s see what we get:


SELECT dob FROM students

Output 2:
dob
2003-07-13
1999-05-26
1999-11-11
2001-10-31
2001-01-25
2002-11-04
1999-06-22
2001-06-05
1999-11-20
2005-03-21
2006-03-11
2000-02-02
The student_course table has the following fields: studentid, courseid, paid,
score.

CREATE TABLE student_course (


studentcourseid int NOT NULL,
studentid int NOT NULL,
courseid int NOT NULL,
paid tinyint(1) NOT NULL DEFAULT '0',
score int NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
We added the score field to the student_course table, not the ‘students’ table,
because the same student may have different scores for different courses.
How can you add a different score to each record at once? We can use the
following query.

UPDATE student_course SET score=FLOOR(50 + RAND() * 51);

Explanation:

The RAND() function generates a random float value between 0 and 1.


The RAND() * 51 expression scales the random value to a range between 0
and 50.999.
The 50 + RAND() * 51 expression shifts the range to between 50 and
100.999.
The FLOOR(50 + RAND() * 51) expression rounds down to the nearest
whole number, giving a range of 50 to 100

Let’s see what we get.

SELECT studentid, courseid, paid, score FROM student_course ORDER BY


score DESC, studentid, courseid;

Output 3:

studentid courseid paid score


7 9 0 100
13 13 0 100
9 1 0 98
2 1 0 94
3 6 1 94
7 6 1 94
6 5 1 93
9 6 1 93
6 2 1 92
7 2 1 86
2 2 1 85
11 1 1 85
3 2 0 83
11 10 1 81
6 1 1 80
9 7 0 77
4 8 1 76
12 12 0 75
6 2 1 74
4 9 0 70
8 5 0 70
4 3 1 69
12 9 1 65
8 11 0 63
10 1 0 62
8 1 1 59
3 3 0 58
10 6 1 57
5 14 0 56
5 6 0 55
10 4 1 55
11 6 0 55
6 6 0 54
4 1 0 52
The ‘schedule’ table has the following fields:
scheduleid, courseid, teacherid, starttime, endtime, startdate, enddate.

CREATE TABLE students (


scheduleid int NOT NULL,
courseid int NOT NULL,
teacherid int NOT NULL,
starttime time DEFAULT NULL,
endtime time DEFAULT NULL,
startdate date DEFAULT NULL,
enddate date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Let’s add values to the start date and end date. For the start date, you can add
values like we did for the student’s birthday. We can assume that each course
lasts one year to add value to the end date. Then, to get the end date, we can
add one year to the start date.
UPDATE schedule SET enddate =DATE_ADD(startdate, INTERVAL 1 YEAR);

Let’s see what we get.

SELECT startdate, enddate FROM schedule;


Output 4:

startdate enddate
2020-01-11 2021-01-11
2020-01-11 2021-01-11
2020-01-12 2021-01-12
2020-01-12 2021-01-12
2020-01-12 2021-01-12
2020-01-12 2021-01-12
2020-01-12 2021-01-12
2020-01-11 2021-01-11
2020-01-12 2021-01-12
2020-01-12 2021-01-12
2024-01-05 2024-12-20
2022-09-01 2023-09-01
2022-09-24 2023-09-24
2022-09-01 2023-09-01
2022-09-01 2023-09-01
2022-09-03 2023-09-03
2022-09-03 2023-09-03
2024-01-05 2024-12-20
2024-01-05 2024-12-20
Then, we may update the ‘teachers’ table and enter the random age at once.
UPDATE teachers SET age = FLOOR(40 + RAND() * 10);

Let’s see what we get.

SELECT firstname, lastname, age FROM teachers;

Output 5:
firstname lastname age
John Smith 49
John Jefferson 49
James Barry 49
Michael Murphy 48
Julia Williams 43
John Niven 43
Christine Merry 45
Greg Gerald 45
Michael Ross 44
Michael Kelly 42
Norm Karon 41
The relationship between the ‘teachers’ table and the courses table is
"MANY TO MANY" because one teacher may teach many courses, and the
same course may be taught by many ‘teachers’. The ‘schedule’ table has a foreign
key for a teacher and course records and links the ‘teachers’ table and courses
table.
To practice SQL, you need to use MySQL database version 9.
First, you need to create tables and insert data to use these tables to learn SQL
with this book later.
I provided Create Table statements so that you can create tables in MySQL.
You can download a txt file from the link at the end of the book.
You can copy and paste these “create table” statements. When you create the
database and tables, you can copy and paste insert statements with the data to
fill all the tables.
If you do not want to recreate the tables on your PC, you can use my web
page for practice.
www.learn-coding.today/continue_learn_sql.php
My website server has all the tables and data used for this book, but my host
database version is not high enough, and some examples will not work.

SIMPLE SELECT STATEMENT


To display data from all fields and all records of the courses table, use the
following query:
SELECT * FROM courses
Output 6:
courseid coursename hours cost
1 Visual Basic 360 1999.95
2 Java 500 2999.95
3 C++ 550 3999.95
4 PHP 300 999.95
5 HTML 200 699.95
6 Pearl 300 1699.95
7 CSS 400 899.95
8 Assembly 400 1699.95
9 JavaScript 200 999.95
10 Python 300 999.95
11 Unix 700 1000
12 Java 500 2999.95
13 C++ 500 2999.95
14 Visual Basic 360 1999.95
15 PHP 300 999.95
16 HTML 200 699.95
17 C# 500 2999.95
18 Assembly 400 1999.95
19 JavaScript 200 999.95
20 Python 300 999.95
21 Unix 700 2999.95
22 CSS 400 999.95
23 Perl 400 999.95
24 C# 400 999.95

You will get the same result if you list all table fields in the SELECT clause.
SELECT courseid, coursename, hours, cost FROM courses.
List only those you need if you do not want to display values from all fields.
Besides, you can choose by which field or fields you want to sort records. For
that, you should add an ORDER BY clause.
SELECT courseid, coursename, hours, cost
FROM courses
ORDER BY cost, coursename

Output 7:

courseid coursename hours cost


5 HTML 200 699.95
16 HTML 200 699.95
7 CSS 400 899.95
24 C# 400 999.95
22 CSS 400 999.95
9 JavaScript 200 999.95
19 JavaScript 200 999.95
23 Perl 400 999.95
4 PHP 300 999.95
15 PHP 300 999.95
10 Python 300 999.95
20 Python 300 999.95
11 Unix 700 1000
8 Assembly 400 1699.95
6 Pearl 300 1699.95
18 Assembly 400 1999.95
1 Visual Basic 360 1999.95
14 Visual Basic 360 1999.95
17 C# 500 2999.95
13 C++ 500 2999.95
2 Java 500 2999.95
12 Java 500 2999.95
21 Unix 700 2999.95
3 C++ 550 3999.95

By default, records are sorted by fields included in the ORDER BY clause in


ascending order. If you want to sort them in descending order, add the DESC
keyword.
SELECT courseid, coursename, hours, cost
FROM courses
ORDER BY cost desc, coursename

Output 8:

courseid coursename hours cost


3 C++ 550 3999.95
17 C# 500 2999.95
13 C++ 500 2999.95
2 Java 500 2999.95
12 Java 500 2999.95
21 Unix 700 2999.95
18 Assembly 400 1999.95
1 Visual Basic 360 1999.95
14 Visual Basic 360 1999.95
8 Assembly 400 1699.95
6 Pearl 300 1699.95
11 Unix 700 1000
24 C# 400 999.95
22 CSS 400 999.95
9 JavaScript 200 999.95
19 JavaScript 200 999.95
23 Perl 400 999.95
4 PHP 300 999.95
15 PHP 300 999.95
10 Python 300 999.95
20 Python 300 999.95
7 CSS 400 899.95
5 HTML 200 699.95
16 HTML 200 699.95
COMPLEX SELECT STATEMENTS
To join three tables, you must join two tables and then consider these two
tables as one. That way, it will be easier to understand how to join the third
table.
List the courses taught by each teacher.
SELECT t.lastname,
c.coursename
FROM (teachers t
INNER JOIN schedule s ON t.teacherid=s.teacherid)
INNER JOIN courses c ON s.courseid=c.courseid;

Output 9:
lastname coursename
Smith C++
Ross Visual Basic
Murphy HTML
Murphy CSS
Murphy Unix
Williams Pearl
Williams Assembly
Barry Visual Basic
Niven Python
Gerald JavaScript
Williams Assembly
Jefferson Java
Jefferson C++
Williams Python
Williams Unix
Kelly PHP
Karon PHP
Jefferson Java
Gerald JavaScript
Initially, we join the ‘teachers’ table with the ‘schedule’ table. To emphasize
this, I enclosed these tables in parentheses. Next, we join the result (in
parentheses) with the third table. Parentheses are optional in MySQL and Oracle
but mandatory in MS Access.
The following query is almost identical to the previous one and returns the
same result.

SELECT
t.lastname,
c.coursename
FROM (teachers t
INNER JOIN schedule s ON s.teacherid=t.teacherid)
INNER JOIN courses c ON c.courseid=s.courseid;

Output 10:
coursename
lastname
Smith C++
Ross Visual Basic
Murphy HTML
Murphy CSS
Murphy Unix
Williams Pearl
Williams Assembly
Barry Visual Basic
Niven Python
Gerald JavaScript
Williams Assembly
Jefferson Java
Jefferson C++
Williams Python
Williams Unix
Kelly PHP
Karon PHP
Jefferson Java
Gerald JavaScript
In the ON statements of the first query, the sequence of IDs matches the
sequence of tables. In the second query, the sequence of IDs in the ON
statements is reversed compared to the sequence of tables.
The first query is preferable for readability and convention. It follows the
more conventional order of specifying the join conditions, i.e., ON clauses
match the order of tables being joined.
It presents a more logical and sequential flow of joins, making it easier for
someone reading the query to understand which tables are being joined and on
what conditions.
Which teachers teach Java?
SELECT DISTINCT t.firstname,
t.lastname,
c.coursename
FROM (teachers t
INNER JOIN schedule s ON t.teacherid=s.teacherid)
INNER JOIN courses c ON s.courseid=c.courseid
AND c.coursename='Java'
ORDER BY t.lastname;

Output 11:

firstname lastname coursename


John Jefferson Java
Find students who take the Java course.
SELECT DISTINCT s.firstname,
s.lastname,
c.coursename
FROM students s
INNER JOIN student_course sc ON s.studentid=sc.studentid
INNER JOIN courses c ON sc.courseid=c.courseid
WHERE c.coursename ='Java'
ORDER BY s.lastname;

Output 12:

firstname lastname coursename


Julia Barklay Java
Cindy Brown Java
John Clifford Java
Michael Johnson Java
By default, MySQL performs case-insensitive string comparisons. It means that
the comparison c.coursename = 'java' will return true regardless of whether
c.coursename is 'java', 'Java', or 'JAVA'.
In contrast, Oracle performs case-sensitive string comparisons by default. It
means that the comparison c.coursename = 'java' will only return true if
c.coursename is exactly 'java'. If it is 'Java', 'JAVA', or any other variation, the
query will not match.

Find teachers who teach C++.


SELECT t.firstname, t.lastname, c.coursename
FROM teachers t
INNER JOIN schedule s ON s.teacherid = t.teacherid
INNER JOIN courses c ON c.courseid = s.courseid
WHERE c.coursename = 'C++';

Output 13:

firstname lastname coursename


John Smith C++
John Jefferson C++

Explanation:
The query effectively retrieves the desired information. Here's a breakdown
of how it works:
It joins the ‘teachers’, schedule, and courses tables based on the teacherid and
courseid foreign key relationships.
The WHERE clause filters the results only to include rows where the
coursename is 'C++'.
The SELECT clause specifies the columns to be retrieved: firstname,
lastname, and coursename.
The resulting output will be a list of teachers who teach the "C++" course,
along with their first, last, and course names.
What if we do not want to select all records a query returns but limit it to
a certain number? The following query demonstrates how to do that.

SELECT DISTINCT s.firstname,


s.lastname,
c.coursename
FROM students s
INNER JOIN student_course sc ON s.studentid=sc.studentid
INNER JOIN courses c ON sc.courseid=c.courseid
ORDER BY s.lastname LIMIT 3;
Output 14:

firstname lastname coursename


Julia Barklay Java
Julia Barklay JavaScript
Cindy Brown Visual Basic

SELECT DISTINCT s.firstname,


s.lastname,
c.coursename
FROM students s
INNER JOIN student_course sc ON s.studentid=sc.studentid
INNER JOIN courses c ON sc.courseid=c.courseid
ORDER BY c.coursename LIMIT 3;

Output 15:

firstname lastname coursename


Molly James Assembly
Michael Johnson C++
Molly James C++
In Oracle, you must use a different syntax:
SELECT DISTINCT s.firstname, s.lastname, c.coursename
FROM students s
INNER JOIN student_course sc ON s.studentid=sc.studentid
INNER JOIN courses c ON sc.courseid=c.courseid
ORDER BY s.lastname
FETCH FIRST 3 ROWS ONLY;
Or you can use the following query:

SELECT * FROM (
SELECT DISTINCT s.firstname,
s.lastname,
c.coursename
FROM students s
INNER JOIN student_course sc ON s.studentid=sc.studentid
INNER JOIN courses c ON sc.courseid=c.courseid
ORDER BY s.lastname )
WHERE ROWNUM <= 3

Find students who have scored 90 or higher in at least one course.


SELECT
s.firstname,
s.lastname,
c.coursename,
sc.score
FROM
students s
INNER JOIN student_course sc
ON s.studentid = sc.studentid
INNER JOIN courses c
ON sc.courseid = c.courseid
where sc.score >=90;
Output 16:
lastname coursename score
firstname
James Folkner Visual Basic 98
Cindy Brown Java 92
Cindy Brown HTML 93
Michael Johnson Pearl 94
Julia Barklay Pearl 94
James Folkner Pearl 93
Julia Barklay JavaScript 100
Ann Williams C++ 100

Explanation:
The objective is to find students with at least one score greater than or equal to
90.
The SELECT Clause lists all fields that need to be selected.

The FROM Clause specifies the primary table from which to select data,
aliasing the ‘students’ table as s.
The INNER JOIN joins the ‘students’ table with the student_course table,
matching records where the studentid in the ‘students’ table equals the studentid
in the student_course table.
The INNER JOIN courses clause joins the student_course table with the
courses table, matching records where the courseid in the student_course table
equals the courseid in the courses table.
The WHERE clause filters the results only to include records with scores
greater than or equal to 90 in the student_course table.
This query retrieves the first name and last name of students, along with their
course names and scores, but only for records where the student's score is 90 or
higher. The query uses inner joins to combine data from the students,
student_course, and courses tables based on matching studentid and courseid
Find the student with the highest score in C++.
SELECT
s.firstname,
s.lastname,
sc.score
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
INNER JOIN courses c ON sc.courseid = c.courseid
WHERE
c.coursename = 'C++'
ORDER BY
sc.score DESC
LIMIT 1;

Output 17:

firstname lastname score


Ann Williams 100
Explanation:
Identify the student who has achieved the highest score in the C++ course.
The SELECT clause lists fields that need to be selected.
The FROM clause specifies the primary table from which to select data,
aliasing the ‘students’ table as s.
The INNER JOIN with student_course joins the ‘students’ table with the
student_course table, matching records where the studentid in the ‘students’
table is equal to the studentid in the student_course table.
The INNER JOIN with courses joins the student_course table with the
courses table, matching records where the courseid in the student_course table
equals the courseid in the courses table.
The WHERE clause filters the results to only include records where the
course name is 'C++'.
The ORDER BY clause orders the results by the score in descending order,
so the highest score appears first.
The LIMIT 1 clause limits the result to just one row, selecting the student
with the highest score in the C++ course.

Find students who attend morning classes.

SELECT DISTINCT s.firstname, s.lastname, sch.starttime


FROM students s
JOIN student_course sc
ON s.studentid = sc.studentid
JOIN schedule sch
ON sc.courseid = sch.courseid
WHERE sch.starttime < '12:00:00'
ORDER BY s.lastname, s.firstname, sch.starttime;

Output 18:

firstname lastname starttime


Julia Barklay 08:00:00
Julia Barklay 09:00:00
Cindy Brown 08:00:00
Cindy Brown 09:00:00
Ryan Brown 08:00:00
Alison Cremette 08:00:00
James Folkner 08:00:00
Michael Holden 08:00:00
Michael Holden 09:00:00
Molly James 08:00:00
Molly James 09:00:00
Michael Johnson 08:00:00
Michael Johnson 09:00:00
Holly Michaels 08:00:00
Holly Michaels 09:00:00

Explanation:
The SELECT DISTINCT selects the distinct first and last names of students
and the schedule start time to avoid duplicates.
The FROM clause specifies the ‘students’ table and gives it an alias ‘s’ for easier
referencing.
The JOIN student_course sc ON s.studentid = sc.studentid clause joins the
‘students’ table with the ‘student_course’ table based on the studentid.
The JOIN schedule sch ON sc.courseid = sch.courseid clause joins the
student_course table with the ‘schedule’ table based on the courseid.
The WHERE sch.starttime < '12:00:00' clause filters the results to include only
schedules where the starttime is before 12:00 PM (noon), indicating morning
courses.
This query will effectively retrieve the names of students who attend morning
courses by joining the relevant tables and applying the appropriate filter.
Find students who attend only afternoon classes and not morning classes.
SELECT DISTINCT s.studentid, s.firstname, s.lastname
FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN schedule sch ON sc.courseid = sch.courseid
WHERE s.studentid NOT IN (
SELECT s.studentid
FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN schedule sch ON sc.courseid = sch.courseid
WHERE sch.starttime < '12:00:00 PM'
);

Output 19:
studentid firstname lastname
12 John Clifford
13 Ann Williams
The subquery fetches student IDs from morning courses.
SELECT s.studentid
FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN schedule sch ON sc.courseid = sch.courseid
WHERE sch.starttime < '12:00:00 PM'

Then, in the main query, we select only IDs that are not included in the
subquery result.
Find students who attend only morning classes and not afternoon classes.
SELECT DISTINCT s.studentid, s.firstname, s.lastname
FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN schedule sch ON sc.courseid = sch.courseid
WHERE s.studentid NOT IN (
SELECT s.studentid
FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN schedule sch ON sc.courseid = sch.courseid
WHERE sch.starttime >= '12:00:00'
);

Output 20:

studentid firstname lastname


3 Michael Johnson
5 Holly Michaels

The subquery of the main query retrieves the student IDs of those who attend
afternoon classes.
Then, in the main query, we select IDs that are not included in the subquery
result.

AGGREGATE FUNCTIONS
How do we understand aggregated functions? Let’s review a simple
example with the COUNT function. Suppose you need to count how
many courses each student attends.

First, we display all the students and courses.


SELECT
c.courseid,
c.coursename,
s.lastname
FROM
students s
JOIN
student_course sc ON s.studentid=sc.studentid
JOIN
courses c ON sc.courseid=c.courseid
ORDER BY s.lastname, c.coursename;

Output 21:
lastname courseid coursename
Barklay 2 Java
Barklay 9 JavaScript
Barklay 6 Pearl
Brown 5 HTML
Brown 2 Java
Brown 2 Java
Brown 6 Pearl
Brown 6 Pearl
Brown 10 Python
Brown 1 Visual Basic
Brown 1 Visual Basic
Clifford 12 Java
Clifford 9 JavaScript
Cremette 5 HTML
Cremette 11 Unix
Cremette 1 Visual Basic
Folkner 7 CSS
Folkner 6 Pearl
Folkner 1 Visual Basic
Holden 6 Pearl
Holden 4 PHP
Holden 1 Visual Basic
James 8 Assembly
James 3 C++
James 9 JavaScript
James 1 Visual Basic
Johnson 3 C++
Johnson 2 Java
Johnson 6 Pearl
Michaels 6 Pearl
Michaels 14 Visual Basic
Williams 13 C++
To count courses for each student you will group records per student.
Let’s start from the bottom.
Lastname Courseid Coursename
Williams 13 C++
Michaels 14 Visual Basic
6 Pearl
Johnson 6 Pearl
2 Java
3 C++
James 1 Visual Basic
9 JavaScript
3 C++
8 Assembly

And so on.

You can do the same with your query using the GROUP BY clause.
Find out how many courses each student attends.
SELECT
s.firstname,
s.lastname,
COUNT(DISTINCT sc.courseid) AS enrolled_courses
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname
ORDER BY
s.lastname;

Output 22:

firstname lastname enrolled_courses


Julia Barklay 3
Ryan Brown 3
Cindy Brown 5
John Clifford 2
Alison Cremette 3
James Folkner 3
Michael Holden 3
Molly James 4
Michael Johnson 3
Holly Michaels 2
Ann Williams 1
As you can see, we got the same result with the query.
Williams attends one course. Michaels attends two courses ;
Johnson attends three courses, and James attends four courses.
Let’s display courses and students sorted by coursename. To save space, I will
select only the first five records.
SELECT
c.courseid,
c.coursename,
s.lastname
FROM
students s
JOIN
student_course sc ON s.studentid=sc.studentid
JOIN
courses c ON sc.courseid=c.courseid
ORDER BY c.coursename, s.lastname
LIMIT 5;

Output 23:
courseid coursename lastname
8 Assembly James
3 C++ James
3 C++ Johnson
13 C++ Williams
7 CSS Folkner
You must group records per course to calculate the number of students for each
course. Then, you will see that one student attends the Assembly language
course, three students attend C++, and one student attends CSS.
.
Courseid Coursename lastname
8 Assembly James
3 C++ James
Johnson
Williams
7 CSS Folkner

You will get the same result with the query using GROUP BY courseid.

SELECT
c.coursename,
COUNT(DISTINCT s.studentid) as students_number
FROM
students s
JOIN
student_course sc ON s.studentid=sc.studentid
JOIN
courses c ON sc.courseid=c.courseid
GROUP BY
c.coursename
ORDER BY c.coursename;

Output 24:
coursename students_number
Assembly 1
C++ 3
CSS 1
HTML 2
Java 4
JavaScript 3
Pearl 7
PHP 1
Python 1
Unix 1
Visual Basic 7
One student attends the Assembly language course, three attend C++, and one
attend CSS.
Find the student who has enrolled in the most courses.
SELECT
s.studentid,
s.firstname,
s.lastname,
COUNT(sc.courseid) AS enrolled_courses
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.studentid, s.firstname, s.lastname
ORDER BY
enrolled_courses DESC
LIMIT 1;

Output 25:
studentid firstname lastname enrolled_courses
6 Cindy Brown 5
How does this query work? First, it gets student information and course
enrollments by JOIN the ‘students’ table with the ‘student_course’ table. Then it
groups the results by studentid, firstname, and lastname to count the number of
courses for each student.
Then, the results are ordered in descending order based on the
enrolled_courses count.
Then, the result is limited to the first row, representing the student with the
most enrolled courses.
Find the average score for each student.
SELECT
s.firstname,
s.lastname,
AVG(sc.score) AS average_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname
Order by s.lastname;

Output 26:
firstname lastname average_score
Julia Barklay 93.3333
Ryan Brown 73.6667
Cindy Brown 78.6000
John Clifford 70.0000
Alison Cremette 64.0000
James Folkner 89.3333
Michael Holden 58.0000
Molly James 66.7500
Michael Johnson 78.3333
Holly Michaels 55.5000
Ann Williams 100.0000
This query selects each student's first and last name and the average score.
Since scores are stored in the student_course table, the query joins the
‘students’ table with the ‘student_course’ table to get student information along
with their scores.
In the GROUP BY clause, the query groups the results by first name and last
name to calculate each student's average score.
The AVG(sc.score) function calculates each student's average score based on
their scores in the student_course table.
This query will effectively calculate the average score for each student in the
database.
Find a student with the highest average score.
SELECT
s.firstname, s.lastname,
ROUND( AVG(sc.score)) AS average_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname
ORDER BY
average_score DESC
LIMIT 1;

Output 27:
firstname lastname average_score
Ann Williams 100
Explanation:
The SELECT clause selects the firstname and lastname and average scores
for each student.
The FROM clause joins the ‘students’ table with the ‘student_course’ table to
get student information along with their scores.
The GROUP BY clause groups the results by firstname and lastname to
calculate the average score for each student.

The AVG function calculates the average score for the student based on their
scores in the student_course table.
The ROUND function rounds values to the whole number.
The ORDER BY average_score DESC clause orders the results in
descending order based on the average_score.
The LIMIT 1 clause limits the result to the first row, representing the student
with the highest average score.
This query will effectively identify the student with the highest average score
across all their courses.
Find a course average score.

SELECT c.coursename, ROUND(AVG(sc.score)) as avg_score


FROM courses c INNER JOIN student_course sc
ON c.courseid=sc.courseid
GROUP BY c.coursename
ORDER BY coursename;

Output 28:

coursename avg_score
Assembly 76
C++ 76
CSS 77
HTML 82
Java 83
JavaScript 78
Pearl 72
PHP 55
Python 81
Unix 63
Visual Basic 73
Find a course with the highest average score.

SELECT c.coursename, ROUND(AVG(sc.score)) as avg_score


FROM courses c INNER JOIN student_course sc
ON c.courseid=sc.courseid
GROUP BY c.coursename
ORDER BY avg_score DESC
LIMIT 1;

Output 29:
coursename avg_score
Java 83

In the above query, we sort records by the score in descending order and take
only the first record representing a student with the highest score.
In the same way, we can find a student with the lowest score if we sort records
in ascending order.
Find a course with the lowest average score.

SELECT c.coursename, ROUND(AVG(sc.score)) as avg_score


FROM courses c INNER JOIN student_course sc
ON c.courseid=sc.courseid
GROUP BY c.coursename
ORDER BY avg_score
LIMIT 1;

Output 30:

coursename avg_score
PHP 55
How to find a teacher who has the most cars
SELECT
t.firstname,
t.lastname,
COUNT(c.cid) AS num_cars
FROM
teachers t
INNER JOIN cars c ON t.teacherid = c.teacherid
GROUP BY
t.firstname, t.lastname
HAVING
COUNT(c.cid) = (
SELECT MAX(car_count)
FROM (
SELECT
COUNT(c2.cid) AS car_count
FROM teachers t2
INNER JOIN cars c2 on c2.teacherid=t2.teacherid
GROUP BY t2.firstname, t2.lastname
) AS MaxCarCounts
);

Output 31:

firstname lastname num_cars


John Smith 2
John Jefferson 2

Explanation:
The innermost Query returns the car count for each teacher.
SELECT
COUNT(c2.cid) AS car_count
FROM
teachers t2
INNER JOIN
cars c2 ON c2.teacherid = t2.teacherid
GROUP BY
t2.firstname, t2.lastname

The middle query selects the maximum car count from the result of the
innermost query.
SELECT MAX(car_count)
FROM (
SELECT
COUNT(c2.cid) AS car_count
FROM
teachers t2
INNER JOIN
cars c2 ON c2.teacherid = t2.teacherid
GROUP BY
t2.firstname, t2.lastname
) AS MaxCarCounts

The outer query retrieves the teachers' first name, last name, and car count and
selects the records matching the maximum car count.
SELECT
t.firstname,
t.lastname,
COUNT(c.cid) AS num_cars
FROM
teachers t
INNER JOIN
cars c ON t.teacherid = c.teacherid
GROUP BY
t.firstname, t.lastname
HAVING
COUNT(c.cid) = (
SELECT MAX(car_count)
FROM (
SELECT
COUNT(c2.cid) AS car_count
FROM
teachers t2
INNER JOIN
cars c2 ON c2.teacherid = t2.teacherid
GROUP BY
t2.firstname, t2.lastname
)

Here is the simplified query that returns the same result.

SELECT
t.firstname,
t.lastname,
COUNT(c.cid) AS num_cars
FROM
teachers t
INNER JOIN
cars c ON t.teacherid = c.teacherid
GROUP BY
t.firstname, t.lastname
HAVING
COUNT(c.cid) = (
SELECT COUNT(c2.cid)
FROM teachers t2
INNER JOIN cars c2 ON t2.teacherid = c2.teacherid
GROUP BY t2.firstname, t2.lastname
ORDER BY COUNT(c2.cid) DESC
LIMIT 1
);

Output 32:
firstname lastname num_cars
John Smith 2
John Jefferson 2
This query achieves the same goal by directly comparing the count of cars for
each teacher to the highest car count found in the inner query.
Explanation:
The innermost query returns the car count for each teacher.
SELECT
COUNT(c2.cid) AS car_count
FROM
teachers t2
INNER JOIN
cars c2 ON c2.teacherid = t2.teacherid
GROUP BY
t2.firstname, t2.lastname

The middle query selects the maximum car count from the result of the
innermost query, ordering them in descending order and limiting the result to
the highest value.
SELECT COUNT(c2.cid)
FROM
teachers t2
INNER JOIN
cars c2 ON t2.teacherid = c2.teacherid
GROUP BY
t2.firstname, t2.lastname
ORDER BY
COUNT(c2.cid) DESC
LIMIT 1

The outer query retrieves the first name, last name, and car count for all teachers
and selects the records that match the maximum car count obtained from the
middle query.
SELECT
t.firstname,
t.lastname,
COUNT(c.cid) AS num_cars
FROM
teachers t
INNER JOIN
cars c ON t.teacherid = c.teacherid
GROUP BY
t.firstname, t.lastname
HAVING
COUNT(c.cid) = (
SELECT COUNT(c2.cid)
FROM teachers t2
INNER JOIN cars c2 ON t2.teacherid = c2.teacherid
GROUP BY t2.firstname, t2.lastname
ORDER BY COUNT(c2.cid) DESC
LIMIT 1
);

How to find a course with a maximum number of students


SELECT
c.coursename,
COUNT(sc.studentid) AS num_students
FROM
courses c
INNER JOIN student_course sc ON c.courseid = sc.courseid
GROUP BY
c.coursename
ORDER BY
num_students DESC
LIMIT 1;

Output 33:
coursename num_students
Visual Basic 8
This query selects the number of students in each course, sorts the records by
the number of students in descending order, and retrieves the first record.
Find average scores for male and female students.
SELECT
s.gender,
ROUND(AVG(sc.score)) AS average_score
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
GROUP BY
s.gender;

Output 34:

gender average_score
male 74
female 75

You can also use a more complex query to compare the average scores for
specific courses. Still, the basic approach involves grouping the data by gender
and calculating the average score for each group.
SELECT
s.gender, c.coursename,
ROUND(AVG(sc.score)) AS average_score
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
INNER JOIN courses c ON c.courseid = sc.courseid
GROUP BY
s.gender, c.coursename order by c.coursename;

Output 35:

gender coursename average_score


female Assembly 76
male C++ 58
female C++ 85
male CSS 77
female HTML 82
female Java 84
male Java 79
male JavaScript 65
female JavaScript 85
male Pearl 75
female Pearl 68
male PHP 55
male Python 81
female Unix 63
male Visual Basic 82
female Visual Basic 62

Find the cost of courses for each student.


SELECT
s.firstname,
s.lastname,
SUM(c.cost) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.lastname, s.firstname;

Output 36:

firstname lastname total_spent


John Clifford 3999.8999633789062
Molly James 7699.799865722656
James Folkner 4599.849914550781
Michael Holden 4699.849914550781
Julia Barklay 5699.849914550781
Ryan Brown 4699.849914550781
Alison Cremette 3699.8999633789062
Holly Michaels 3699.89990234375
Cindy Brown 10399.749816894531
Michael Johnson 7699.849853515625
Ann Williams 2999.949951171875
Add the ROUND function.
SELECT
s.firstname,
s.lastname,
ROUND(SUM(c.cost), 2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.firstname, s.lastname;

Output 37:

firstname lastname total_spent


John Clifford 3999.9
Molly James 8699.8
James Folkner 4599.85
Michael Holden 4699.85
Julia Barklay 5699.85
Ryan Brown 4699.85
Alison Cremette 3699.9
Holly Michaels 3699.9
Cindy Brown 10399.75
Michael Johnson 8699.85
Ann Williams 2999.95
The query above assumes that all enrolled courses have been paid for. If you
need to consider the paid column in the student_course table, you can modify
the query as follows:
SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END)) AS
total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid, s.firstname, s.lastname
Order by total_spent;

Output 38:

studentid firstname lastname total_spent


5 Holly Michaels 0
13 Ann Williams 0
12 John Clifford 1000
9 James Folkner 1700
3 Michael Johnson 1700
8 Alison Cremette 2000
10 Michael Holden 2700
11 Ryan Brown 3000
7 Julia Barklay 4700
4 Molly James 5700
6 Cindy Brown 8700

This version of the query only includes the cost of courses for which payment
has been made.

Find a student whose courses cost him the most.


SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(c.cost)) AS total_cost
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
SUM(c.cost) = (SELECT MAX(total)
FROM
(SELECT SUM(c.cost) AS total
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid) cost_most);

Output 39:

studentid firstname lastname total_cost


6 Cindy Brown 10400
Explanation:
The main query aims to find the student whose courses have the highest total
cost.
The innermost subquery calculates the total cost of courses for each student.
SELECT
SUM(c.cost) AS total
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
The middle query finds the maximum total cost from the results of the
innermost query.
SELECT MAX(total)
FROM (
SELECT
SUM(c.cost) AS total
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
) cost_most

The outer query retrieves the student ID, first name, last name, and the total
cost of courses for students whose total cost matches the maximum total cost
found in the middle query.
To check if the student actually paid, we use a CASE statement.

SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END) = (SELECT
MAX(total_paid)
FROM
(SELECT SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END)
AS total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid) paid_most);

Output 40:
studentid firstname lastname total_spent
6 Cindy Brown 8699.8

Now we can see that all of Cindy’s courses cost $10,400, but she paid only
$8,700
Try to write a query to find a student who paid the least without looking at the
query below.
SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END)) = (SELECT
MIN(total_paid)
FROM
(SELECT ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE
0 END)) AS total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid) paid_least);

Output 41:
studentid firstname lastname total_spent
5 Holly Michaels 0
13 Ann Williams 0
This query returned students who have not paid. To exclude students who have
not paid at all and find those who have paid but paid the least, we can modify
our query by ensuring that only students with a positive
total_spent are considered. Here's the updated query:
SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) > 0 AND
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) = (
SELECT MIN(total_paid)
FROM (
SELECT
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) >
0
) AS paid_least
);

Output 42:
studentid firstname lastname total_spent
12 John Clifford 999.95
Explanation:
The main query aims to find the students who have paid the least, excluding
those who haven't paid anything at all.
The main query retrieves the student ID, first name, last name, and the total
amount spent by each student, rounded to two decimal places.
The JOIN clauses combine the students, student_course, and courses tables
based on their relationships.
The GROUP BY clause groups the results by student ID, first name, and last
name.
The HAVING clause filters the results to include only students who have spent
a positive amount and whose total spent matches the minimum amount spent
by any student who has paid.
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) > 0 AND

The subquery calculates the minimum amount spent by any student who has
paid.
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) = (
SELECT MIN(total_paid)
FROM (
SELECT
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) > 0
) AS paid_least
);

The innermost query calculates the total amount spent by each student who has
paid.
SELECT
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) > 0

The middle query selects the minimum total amount paid from the results of the
innermost query.
SELECT MIN(total_paid)
FROM (
SELECT
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) AS
total_paid
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.studentid
HAVING
ROUND(SUM(CASE WHEN sc.paid = 1 THEN c.cost ELSE 0 END), 2) > 0
) AS paid_least

Find out which courses the youngest student attends.


SELECT
s.lastname,
c.coursename,
MIN(s.age) AS youngest_age
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.lastname, c.coursename
ORDER BY
youngest_age
LIMIT 1;
LIMIT 1;

Output 42:
lastname coursename youngest_age
Clifford JavaScript 18
This query selects all students' records, sorts them in ascending order, and
returns the first record with the youngest age.
Find which course is attended by the oldest student.
SELECT
s.lastname,
c.coursename,
MAX(s.age) AS oldest_age
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.lastname, c.coursename
ORDER BY
oldest_age DESC
LIMIT 1;

Output 43:

lastname coursename oldest_age


James Assembly 30

Find the course with the youngest teacher.

SELECT
t.lastname,
c.coursename,
MIN(t.age) AS youngest_teacher
FROM
teachers t
JOIN
schedule s ON t.teacherid = s.teacherid
JOIN
courses c ON s.courseid = c.courseid
GROUP BY
t.lastname, c.coursename
ORDER BY
youngest_teacher
LIMIT 1;

Output 44:

lastname coursename youngest_teacher


Karon PHP 41
Find the course with the oldest teacher.
SELECT
t.lastname,
c.coursename,
MAX(t.age) AS oldest_teacher
FROM
teachers t
JOIN
schedule s ON t.teacherid = s.teacherid
JOIN
courses c ON s.courseid = c.courseid
GROUP BY
t.lastname, c.coursename
ORDER BY
oldest_teacher DESC
LIMIT 1;

Output 45:
lastname coursename oldest_teacher
Smith C++ 49
Find a male student who has the lowest average score.
SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname
ORDER BY
avg_score ASC
LIMIT 1;
Output 46:
lastname avg_score

Holden 58.0000
Explanation:
The Join clause joins the students and student_course tables to get
student information and their scores.
The WHERE s.gender = 'male' clause filters the results to include only male
students.
The GROUP BY clause groups the results by s.lastname to calculate the average
score for each male student.
The ORDER BY clause sorts the average score in ascending order.
The Limit to one-row clause selects only the first-row student, who will be the
male student with the lowest average score.
Find a female student with the lowest average score.
SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname
ORDER BY
avg_score ASC
LIMIT 1;

Output 47:
lastname avg_score
Michaels 55.5000

Let’s build a query that finds the male and the female student with the lowest
average score.
SELECT
s.gender,
s.firstname,
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.gender, s.firstname, s.lastname
HAVING
(s.gender = 'male' AND AVG(sc.score) = (SELECT MIN(avg_score)
FROM
(SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname) AS male_subquery))
OR
(s.gender = 'female' AND AVG(sc.score) = (SELECT MIN(avg_score)
FROM
(SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname) AS female_subquery));

Output 48:
gender firstname lastname avg_score
male Michael Holden 58.0000
female Holly Michaels 55.5000

Explanation:
The main query retrieves the gender, first name, last name, and average score of
students.
The JOIN clause combines the students and student_course tables based on the
student ID.
The GROUP BY clause groups the results by gender, first name, and last name.
The HAVING clause filters the results based on the conditions specified for
each gender.
The HAVING clause checks if the student's gender is 'male' and if their average
score equals the minimum average score among all male students.
Alternatively, it checks if the student's gender is 'female' and if their average
score equals the minimum average score among all female students.
The HAVING clause has two subqueries, one for each gender (male and
female).
The innermost subqueries calculate the average score for each student within
the respective gender group.
1. SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname

2. SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname

The middle subqueries find the minimum average score from the results of the
innermost subqueries for each gender.
1. SELECT MIN(avg_score)
FROM
(SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname) AS male_subquery
2. SELECT MIN(avg_score)
FROM
(SELECT
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname) AS female_subquery

The outer query retrieves the gender, first name, last name, and average score of
students whose average score matches the minimum average score obtained
from the respective middle subqueries for each gender.
We can get the same result with the query that uses UNION.
SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname
UNION ALL

SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname
ORDER BY
avg_score ASC
LIMIT 2;
Output 49:
gender firstname lastname avg_score
male Michael Holden 58.0000
female Holly Michaels 55.5000
When using the UNION, ensure each query returns the same number of
columns.
Let’s create one more query with UNION.
Find the male and the female student with the highest average score.
SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.lastname
UNION ALL

SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname
ORDER BY
avg_score DESC
LIMIT 2;

Output 50:
lastname avg_score

Williams 100.0000
Barklay 93.3333
Find Students with Scores Above or Below Average.
SELECT
s.studentid,
s.firstname,
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
AVG(sc.score) > (SELECT AVG(score) FROM student_course)

UNION ALL

SELECT
s.studentid,
s.firstname,
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
AVG(sc.score) < (SELECT AVG(score) FROM student_course);

Output 51:

studentid firstname lastname avg_score


9 James Folkner 89.3333
7 Julia Barklay 93.3333
6 Cindy Brown 78.6000
3 Michael Johnson 78.3333
13 Ann Williams 100.0000
12 John Clifford 70.0000
4 Molly James 66.7500
10 Michael Holden 58.0000
11 Ryan Brown 73.6667
8 Alison Cremette 64.0000
5 Holly Michaels 55.5000
Explanation:

The query aims to find students whose average scores are either above or below
the overall average score of all students.
The first subquery retrieves students whose average scores are above average.
SELECT
s.studentid,
s.firstname,
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
AVG(sc.score) > (SELECT AVG(score) FROM student_course)

The second subquery retrieves students whose average scores are below average.
SELECT
s.studentid,
s.firstname,
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.studentid, s.firstname, s.lastname
HAVING
AVG(sc.score) < (SELECT AVG(score) FROM student_course)

The UNION ALL operator combines the results of the two subqueries,
ensuring that both sets of students (those with above-average scores and those
with below-average scores) are included in the final result.
We can find the average score to check if our result is correct.
SELECT AVG(score) FROM student_course
Output 52:
AVG(score)
75.2941
Our query is correct. All students' average scores are above or below the overall
average score.
Let’s find students who enrolled in C++ or Java classes using the UNION
operator.
SELECT
s.studentid,
s.firstname,
s.lastname,
c.coursename
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = 'C++'

UNION ALL

SELECT
s.studentid,
s.firstname,
s.lastname,
c.coursename
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = 'Java';

Output 53:

studentid firstname lastname coursename


3 Michael Johnson C++
4 Molly James C++
13 Ann Williams C++
7 Julia Barklay Java
6 Cindy Brown Java
3 Michael Johnson Java
6 Cindy Brown Java
12 John Clifford Java
Find and display pairs of students whose average scores differ by less than 2
points.
SELECT
s1.lastname AS lastname1,
s2.lastname AS lastname2,
s1.avg_score1,
s2.avg_score2,
ABS(s1.avg_score1 - s2.avg_score2) AS score_diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score1
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score2
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.avg_score1 - s2.avg_score2 BETWEEN -2 AND 2
AND s1.lastname != s2.lastname;

Output 54:
lastname1 lastname2 avg_score1 avg_score2 score_diff
Johnson Brown 78.3333 76.7500 1.5833
Brown Johnson 76.7500 78.3333 1.5833
Explanation:
The query aims to find and display pairs of students whose average scores differ
by less than 2 points.
The first subquery, s1, calculates the average score for each student based on
their last name.
The JOIN clause combines the students and student_course tables based on the
student ID.
The GROUP BY clause groups the results by the student's last name.
The subquery returns the student's last name and average score as avg_score1.
(SELECT
s.lastname,
AVG(sc.score) AS avg_score1
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s1

The second subquery, s2, is similar to s1 and calculates the average score for
each student based on their last name. It returns the students' last names and
average scores as avg_score2.
(SELECT
s.lastname,
AVG(sc.score) AS avg_score2
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2

The main query joins the results of the two subqueries, s1 and s2, to compare
the average scores of different students.
The ON clause ensures that the average scores differ by less than 2 points
(BETWEEN -2 AND 2).
It also ensures that the last names of the students being compared are different
(s1.lastname != s2.lastname).
The ABS() function calculates the absolute difference between the two students'
average scores.
The SELECT statement retrieves the two students' last names, their respective
average scores, and the absolute difference in their scores.
Let's modify this query so that one student is male and the other female.
SELECT
s1.lastname AS lastname1,
s2.lastname AS lastname2,
s1.avg_score as avg_score1,
s2.avg_score as avg_score2,
ABS(s1.avg_score - s2.avg_score) AS score_difference
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname) AS s2 ON ABS(s1.avg_score - s2.avg_score) <= 2;

Output 55:

lastname1 lastname2 avg_score1 avg_score2 score_difference


Johnson Brown 78.3333 78.6000 0.2667
The first subquery (s1) now includes WHERE s.gender = 'male', ensuring it only
considers male students.
The second subquery (s2) now includes WHERE s.gender = 'female', ensuring it
only considers female students.
This modified query will find pairs of students where one is male and the other
is female, and the difference between their average scores is less than or equal to
2.
This query effectively addresses the requirement to find pairs of students with
different genders and a slight score difference.
Let's modify this query to display genders and not to display the score
difference.
SELECT
'Male' AS Gender,
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
'Female' AS Gender,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'male'
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
s.gender = 'female'
GROUP BY
s.lastname) AS s2 ON ABS(s1.avg_score - s2.avg_score) <= 2;

Output 56:

Gender Lastname1 Avg_Score1 Gender Lastname2 Avg_Score2


Male Johnson 78.3333 Female Brown 78.6000
This query will now display each student's gender, last name, and respective
average scores while still filtering for pairs of students (male and female) whose
average scores differ by less than or equal to 2.
Find two students who have the greatest difference in average scores.
SELECT
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2,
ABS(s1.avg_score - s2.avg_score) AS Score_Diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.lastname != s2.lastname
ORDER BY
ABS(s1.avg_score - s2.avg_score) DESC
LIMIT 1;

Output 57:
Lastname1 Avg_Score1 Lastname2 Avg_Score2 Score_Diff
Williams 100.0000 Michaels 55.5000 44.5000
Two subqueries calculate the average score for each student.
The subqueries are joined on the condition s1.lastname != s2.lastname to ensure
we compare different students.
The function ABS(s1.avg_score - s2.avg_score) calculates the absolute
difference between the two students' average scores.
The ORDER BY clause sorts the results in descending order based on the
absolute difference in average scores.
The LIMIT 1 clause selects only the first row, representing the pair of students
with the greatest difference in average scores.
This query will find the two students with the largest difference in their average
scores.
This query assumes no duplicate first and last names are in the ‘students’ table.
If duplicate names are possible, you might need to include the studentid in the
join condition to ensure that the comparison is made between truly distinct
students.
Let’s see what our query returns without limit.
SELECT
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2,
ABS(s1.avg_score - s2.avg_score) AS Score_Diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.lastname != s2.lastname
ORDER BY
ABS(s1.avg_score - s2.avg_score) DESC

Output 58:
99 rows in set (0.02 sec)
We have eleven students in total. Thus, 11 people can form 55 unique pairs.

We got 99 records.
The result shows that this query shows the difference between a pair of
students' scores twice. One record shows the difference between Williams and
Michaels, and the second record shows the difference between Michael and
Williams.
For instance, if you have students with last names A and B, your query will
generate both pairs (A, B) and (B, A), while you only need one of those pairs.
To avoid this redundancy and get unique pairs, you can modify your query by
ensuring that each pair is only created once. One way to do this is by adding a
condition to ensure the second student's last name is lexicographically greater
than the first student's last name. Here's the updated query:
SELECT
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2,
ABS(s1.avg_score - s2.avg_score) AS Score_Diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.lastname < s2.lastname
ORDER BY
ABS(s1.avg_score - s2.avg_score) DESC

Output 59:
Lastname1 Avg_Score1 Lastname2 Avg_Score2 Score_Diff
Michaels 55.5000 Williams 100.0000 44.5000
Holden 58.0000 Williams 100.0000 42.0000
Barklay 93.3333 Michaels 55.5000 37.8333
Cremette 64.0000 Williams 100.0000 36.0000
Barklay 93.3333 Holden 58.0000 35.3333
Folkner 89.3333 Michaels 55.5000 33.8333
James 66.7500 Williams 100.0000 33.2500
Folkner 89.3333 Holden 58.0000 31.3333
Clifford 70.0000 Williams 100.0000 30.0000
Barklay 93.3333 Cremette 64.0000 29.3333
Barklay 93.3333 James 66.7500 26.5833
Brown 73.6667 Williams 100.0000 26.3333
Cremette 64.0000 Folkner 89.3333 25.3333
Barklay 93.3333 Clifford 70.0000 23.3333
Brown 78.6000 Michaels 55.5000 23.1000
Johnson 78.3333 Michaels 55.5000 22.8333
Folkner 89.3333 James 66.7500 22.5833
Johnson 78.3333 Williams 100.0000 21.6667
Brown 78.6000 Williams 100.0000 21.4000
Brown 78.6000 Holden 58.0000 20.6000
Holden 58.0000 Johnson 78.3333 20.3333
Clifford 70.0000 Folkner 89.3333 19.3333
Brown 73.6667 Michaels 55.5000 18.1667
Barklay 93.3333 Brown 76.7500 16.5833
Brown 73.6667 Holden 58.0000 15.6667
Brown 73.6667 Folkner 89.3333 15.6666
Barklay 93.3333 Johnson 78.3333 15.0000
Brown 78.6000 Cremette 64.0000 14.6000
Clifford 70.0000 Michaels 55.5000 14.5000
Cremette 64.0000 Johnson 78.3333 14.3333
Clifford 70.0000 Holden 58.0000 12.0000
Brown 78.6000 James 66.7500 11.8500
James 66.7500 Johnson 78.3333 11.5833
James 66.7500 Michaels 55.5000 11.2500
Folkner 89.3333 Johnson 78.3333 11.0000
Brown 78.6000 Folkner 89.3333 10.7333
Folkner 89.3333 Williams 100.0000 10.6667
Brown 73.6667 Cremette 64.0000 9.6667
Holden 58.0000 James 66.7500 8.7500
Brown 78.6000 Clifford 70.0000 8.6000
Cremette 64.0000 Michaels 55.5000 8.5000
Clifford 70.0000 Johnson 78.3333 8.3333
Brown 73.6667 James 66.7500 6.9167
Barklay 93.3333 Williams 100.0000 6.6667
Clifford 70.0000 Cremette 64.0000 6.0000
Cremette 64.0000 Holden 58.0000 6.0000
Brown 73.6667 Johnson 78.3333 4.6666
Barklay 93.3333 Folkner 89.3333 4.0000
Brown 73.6667 Clifford 70.0000 3.6667
Clifford 70.0000 James 66.7500 3.2500
Cremette 64.0000 James 66.7500 2.7500
Holden 58.0000 Michaels 55.5000 2.5000
Brown 78.6000 Johnson 78.3333 0.2667

Now, we have only 53 pairs of students.


Explanation:
The query aims to find and display the pair of students who have the greatest
difference in their average scores.
The subquery s1 calculates the average score for each student based on their last
name.
The JOIN clause combines the students and student_course tables based on the
student ID.
The GROUP BY clause groups the results by the student's first and last names.
The subquery s1 returns the student's last name and average score as avg_score.
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1

This subquery s2 is similar to s1 and calculates the average score for each
student based on their last name.
It returns the student's last name and average score, aliased as avg_score.
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2

The main query joins the results of the two subqueries, s1 and s2, to compare
the average scores of different students.
The ON clause ensures that the last names of the students being compared are
different (s1.lastname < s2.lastname). This condition ensures that the last names
are different and that the second student's last name is lexicographically greater
than the first student's last name. It prevents the same pair of students from
being compared twice (e.g., comparing student A with student B and then
student B with student A).
The ORDER BY clause sorts the results by the absolute difference in average
scores in descending order (ABS(s1.avg_score - s2.avg_score) DESC).
The SELECT statement retrieves the last names of the two students, their
respective average scores, and the absolute difference in their scores.
Let’s add LIMIT1 and see if we get the same result.
SELECT
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2,
ABS(s1.avg_score - s2.avg_score) AS Score_Diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.lastname < s2.lastname
ORDER BY
ABS(s1.avg_score - s2.avg_score) DESC
LIMIT 1;

Output 60:

Lastname1 Avg_Score1 Lastname2 Avg_Score2 Score_Diff


Michaels 55.5000 Williams 100.0000 44.5000
The result is the same.
Find a pair of students with the different but with the closest average scores.
SELECT
s1.lastname AS Lastname1,
s1.avg_score AS Avg_Score1,
s2.lastname AS Lastname2,
s2.avg_score AS Avg_Score2,
ABS(s1.avg_score - s2.avg_score) AS Score_Diff
FROM
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.firstname, s.lastname) AS s1
JOIN
(SELECT
s.lastname,
AVG(sc.score) AS avg_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
GROUP BY
s.lastname) AS s2 ON s1.lastname < s2.lastname
ORDER BY
ABS(s1.avg_score - s2.avg_score) ASC
LIMIT 1;

Output 61:
Lastname1 Avg_Score1 Lastname2 Avg_Score2 Score_Diff
Brown 78.6000 Johnson 78.3333 0.2667
This query finds a pair of students with the closest average scores.
Two subqueries calculate the average score for each student, aliased as s1 and
s2.
The JOIN clause uses s1.lastname < s2.lastname to ensure that each pair of
students is considered only once.
ORDER BY ABS(s1.avg_score - s2.avg_score) ASC sorts the results in
ascending order based on the absolute difference in average scores.
LIMIT 1 selects only the first row, representing the pair of students with the
smallest difference in average scores.
WINDOW FUNCTIONS
Let's analyze the simplest query that uses the OVER PARTITION BY window
function.
SELECT
studentid,
courseid,
score,
SUM(score) OVER (PARTITION BY courseid ORDER BY studentid) AS
running_total
FROM
student_course;

Output 62:
studentid courseid score running_total
2 1 94 94
4 1 52 146
6 1 80 226
8 1 59 285
9 1 98 383
10 1 62 445
11 1 85 530
2 2 85 85
3 2 83 168
6 2 92 334
6 2 74 334
7 2 86 420
3 3 58 58
4 3 69 127
10 4 55 55
6 5 93 93
8 5 70 163
3 6 94 94
5 6 55 149
6 6 54 203
7 6 94 297
9 6 93 390
10 6 57 447
11 6 55 502
9 7 77 77
4 8 76 76
4 9 70 70
7 9 100 170
12 9 65 235
11 10 81 81
8 11 63 63
12 12 75 75
13 13 100 100
5 14 56 56
In the first row, the score is 94, and the running_total is 94.
In the second row, the score is 52, and the running_total=94 + 52 = 146.
In the third row, the score is 80, and the running_total=94 + 52 + 80 = 226.
And so on until the courseid is changed.
In the last row of the courseid equal to 1, we have the score = 85 and the
total_running = 94 + 52 + 80 + 59 + 98 + 62 + 85 = 530.

Explanation:
The query selects the following columns from the student_course table:
the studentid, which is the unique identifier for each student,
the courseid, which is the unique identifier for each course,
the score received by the student in the course.
The SUM(score) OVER (PARTITION BY courseid ORDER BY
studentid) AS running_total part of the query calculates a running total of
scores within each course.
The SUM(score) aggregate function calculates the sum of the score column.
The OVER (PARTITION BY courseid ORDER BY studentid is a window
function that partitions the data by courseid and sorts it by studentid. For each
row, it calculates the running total of scores up to that row within the same
partition (course).
We assign the running_total alias to the result of the window function.
The query calculates a running total of scores for each student within each
course. The running total is accumulated as the rows are ordered by studentid.
Calculate the running count of courses for each student.
SELECT
studentid,
courseid,
score,
count(courseid) OVER (PARTITION BY studentid ORDER BY courseid) AS
running_total
FROM
student_course;

Output 63:
studentid courseid score running_total
2 1 94 1
2 2 85 2
3 2 83 1
3 3 58 2
3 6 94 3
4 1 52 1
4 3 69 2
4 8 76 3
4 9 70 4
5 6 55 1
5 14 56 2
6 1 80 1
6 2 74 3
6 2 92 3
6 5 93 4
6 6 54 5
7 2 86 1
7 6 94 2
7 9 100 3
8 1 59 1
8 5 70 2
8 11 63 3
9 1 98 1
9 6 93 2
9 7 77 3
10 1 62 1
10 4 55 2
10 6 57 3
11 1 85 1
11 6 55 2
11 10 81 3
12 9 65 1
12 12 75 2
13 13 100 1

Let's break down this query step by step.


The query selects the following columns from the student_course table:
studentid, courseid, score.
The count(courseid) OVER (PARTITION BY studentid ORDER BY courseid)
AS running_total part of the query calculates each student's running count of
courses.
The count(courseid) aggregate function counts the number of courseid values.
The OVER (PARTITION BY studentid ORDER BY courseid) is a window
function that partitions the data by studentid and orders it by courseid. For each
row, it calculates the running total of courses up to that row within the same
partition (student).
The running_total is an alias we assign to the result of the window function.
The query calculates a running total of the number of courses a student has
taken. The running total is accumulated as the rows are ordered by courseid.
Calculate the running count of students for each course.
SELECT
studentid,
courseid,
score,
count(studentid) OVER (PARTITION BY courseid ORDER BY studentid) AS
running_total
FROM
student_course;

Output 64:
studentid courseid score running_total
2 1 94 1
4 1 52 2
6 1 80 3
8 1 59 4
9 1 98 5
10 1 62 6
11 1 85 7
2 2 85 1
3 2 83 2
6 2 92 4
6 2 74 4
7 2 86 5
3 3 58 1
4 3 69 2
10 4 55 1
6 5 93 1
8 5 70 2
3 6 94 1
5 6 55 2
6 6 54 3
7 6 94 4
9 6 93 5
10 6 57 6
11 6 55 7
9 7 77 1
4 8 76 1
4 9 70 1
7 9 100 2
12 9 65 3
11 10 81 1
8 11 63 1
12 12 75 1
13 13 100 1
5 14 56 1
Let's break down this query step by step.
The query selects the following columns from the student_course table:
studentid, courseid, and course.
The count(studentid) OVER (PARTITION BY courseid ORDER BY
studentid) AS running_total calculates
a running count of students for each course.
The count(studentid) aggregate function counts the number of studentid values.
The OVER (PARTITION BY courseid ORDER BY studentid) is a window
function that partitions the data by courseid and orders it by studentid. For each
row, it calculates the running total of students up to that row within the same
partition (course).
The running_total is an alias we assign to the result of the window function.
The query calculates a running total of the number of students in each course.
The running total is accumulated as the rows are ordered by studentid.

THE RANK AND DENSE_RANK FUNCTIONS


What is the difference between RANK OVER PARTITION and
DENSE_RANK OVER PARTITION functions?
RANK and DENSE_RANK rank rows within a partition of a result set.
However, they differ in handling ties (i.e., rows with the same value).
The RANK function assigns a unique rank to each distinct row within a
partition. If there are ties, the same rank is assigned to the tied rows, and the
next rank is skipped accordingly. For example, if two rows are tied for rank 1,
the next row will be ranked 3.
The DENSE_RANK function is similar to RANK but doesn't skip ranks. If
there are ties, the same rank is assigned to the tied rows, and the next rank
continues sequentially. For example, if two rows are tied for rank 1, the next
row will be ranked 2.
Here's a simple example using both functions:
SELECT
studentid,
score,
RANK() OVER (PARTITION BY courseid ORDER BY score DESC) AS rank,
DENSE_RANK() OVER (PARTITION BY courseid ORDER BY score DESC) AS
dense_rank
FROM
student_course;

Let's run this query.


ERROR 1064 (42000): You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax to use near
'rank,
DENSE_RANK() OVER (PARTITION BY courseid ORDER BY score
DESC) AS dense_r' at line 4
We got an error because we used the reserved words RANK and
DENSE_RANK as column aliases. Here is the correct query.
SELECT
studentid,
score,
RANK() OVER (PARTITION BY courseid ORDER BY score DESC) AS st_rank,
DENSE_RANK() OVER (PARTITION BY courseid ORDER BY score DESC) AS
st_dense_rank
FROM
student_course;

In this example, the rows within each courseid are partitioned and ranked based
on their score. The RANK function may have gaps in the ranking sequence
when there are ties, whereas DENSE_RANK will not have any gaps.
Output 65:
studentid courseid score st_rank st_dense_rank
9 1 98 1 1
2 1 94 2 2
11 1 85 3 3
6 1 80 4 4
10 1 62 5 5
8 1 59 6 6
4 1 52 7 7
6 2 92 1 1
7 2 86 2 2
2 2 85 3 3
3 2 83 4 4
6 2 74 5 5
4 3 69 1 1
3 3 58 2 2
10 4 55 1 1
6 5 93 1 1
8 5 70 2 2
3 6 94 1 1
7 6 94 1 1
9 6 93 3 2
10 6 57 4 3
5 6 55 5 4
11 6 55 5 4
6 6 54 7 5
9 7 77 1 1
4 8 76 1 1
7 9 100 1 1
4 9 70 2 2
12 9 65 3 3
11 10 81 1 1
8 11 63 1 1
12 12 75 1 1
13 13 100 1 1
5 14 56 1 1
Explanation:

The query selects the following columns from the student_course table:
studentid, courseid, score, st_rank, st_dense_rank.
The studentid is the unique identifier for each student;
The score is the score received by the student in the course; the
The “RANK() OVER (PARTITION BY courseid ORDER BY score DESC)
AS st_rank” clause calculates the rank of each student within their course.
The “DENSE_RANK() OVER (PARTITION BY courseid ORDER BY score
DESC) AS st_dense_rank” clause calculates the rank of each student within
their course but handles ties differently.
For example, for courseid 1, we have seven students, and their rank varies from
98 to 52. There are no duplicate scores, and the rank is the same as dense_rank,
from 1 to 7.
For courseid 6, we have seven students, and their score varies from 94 to 54.
We have two duplicate scores equal to 94. The first rank and dense_rank for the
94 score are the same and equal to 1. For the 93 score, the rank equals 3, while
the dense_rank equals 2.
If two rows are tied for rank 1, the next row will be ranked 3 by the rank
function and 2 by the dense_rank function.
Find the top 3 class ranks.
SELECT
s.firstname,
s.lastname,
c.coursename,
sc.score,
DENSE_RANK() OVER (PARTITION BY c.coursename ORDER BY sc.score DESC)
AS class_rank
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE class_rank <=3
ORDER BY
sc.score desc, class_rank, c.coursename;

Let's run this query. We received an error.


ERROR 1054 (42S22): Unknown column 'class_rank' in 'where clause.'
This query produces an error because the class_rank field does not exist in the
students, student_course, or courses table.
We cannot use the class_rank field in the WHERE clause.
To fix this problem, add an outer query and assign a "subquery” alias to the
inner query.
Here is the correct query.
SELECT subquery.firstname, subquery.lastname, subquery.coursename,
subquery.score, subquery.class_rank FROM
(SELECT s.firstname,
s.lastname,
c.coursename,
sc.score,
DENSE_RANK() OVER (PARTITION BY c.coursename ORDER BY sc.score DESC)
AS class_rank
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid) as subquery
WHERE subquery.class_rank <=3
ORDER BY
subquery.coursename, subquery.class_rank, subquery.score desc;

Output 66:
firstname lastname coursename score class_rank
Molly James Assembly 76 1
Ann Williams C++ 100 1
Molly James C++ 69 2
Michael Johnson C++ 58 3
James Folkner CSS 77 1
Cindy Brown HTML 93 1
Alison Cremette HTML 70 2
Cindy Brown Java 92 1
Julia Barklay Java 86 2
Michael Johnson Java 83 3
Julia Barklay JavaScript 100 1
Molly James JavaScript 70 2
John Clifford JavaScript 65 3
Michael Johnson Pearl 94 1
Julia Barklay Pearl 94 1
James Folkner Pearl 93 2
Michael Holden Pearl 57 3
Michael Holden PHP 55 1
Ryan Brown Python 81 1
Alison Cremette Unix 63 1
James Folkner Visual Basic 98 1
Ryan Brown Visual Basic 85 2
Cindy Brown Visual Basic 80 3
Explanation:
The query creates a subquery named subquery that gathers detailed student
information along with their ranks within each course.
SELECT
s.firstname,
s.lastname,
c.coursename,
sc.score,
DENSE_RANK() OVER (PARTITION BY c.coursename ORDER BY sc.score DESC)
AS class_rank
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid

The SELECT clause retrieves firstname, lastname, coursename, and score from
the joined tables.
The DENSE_RANK() function ranks students within each course
(c.coursename) based on their scores (sc.score) in descending order. This rank is
given the alias class_rank.
The main outer query then filters and orders the results from the subquery to
meet the specified criteria.
The ORDER BY clause orders the results by class_rank, coursename, and score
in descending order to present the data in a logical and readable format.
This query finds the top 3 students in each course based on their scores. It does
this by ranking students within each course and then filtering to include only
those with a rank of 3 or higher. The results are then ordered to show the top-
performing students in a structured and easy-to-read format.
Let’s create a query ranking students within each course and display the
student's first and last names.
SELECT
s.firstname,
s.lastname,
sc.courseid,
sc.score,
RANK() OVER (PARTITION BY sc.courseid ORDER BY sc.score DESC) AS
rank_in_course
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
ORDER BY sc.courseid, rank_in_course;

Output 67:
firstname lastname courseid score rank_in_course
James Folkner 1 98 1
Ryan Brown 1 85 2
Cindy Brown 1 80 3
Michael Holden 1 62 4
Alison Cremette 1 59 5
Molly James 1 52 6
Cindy Brown 2 92 1
Julia Barklay 2 86 2
Michael Johnson 2 83 3
Cindy Brown 2 74 4
Molly James 3 69 1
Michael Johnson 3 58 2
Michael Holden 4 55 1
Cindy Brown 5 93 1
Alison Cremette 5 70 2
Michael Johnson 6 94 1
Julia Barklay 6 94 1
James Folkner 6 93 3
Michael Holden 6 57 4
Holly Michaels 6 55 5
Ryan Brown 6 55 5
Cindy Brown 6 54 7
James Folkner 7 77 1
Molly James 8 76 1
Julia Barklay 9 100 1
Molly James 9 70 2
John Clifford 9 65 3
Ryan Brown 10 81 1
Alison Cremette 11 63 1
John Clifford 12 75 1
Ann Williams 13 100 1
Holly Michaels 14 56 1
The next query ranks students within each course based on their scores.
Finding the Class Rank of Each Student Within a Course using the
DENSE_RANK() function.
SELECT
s.firstname,
s.lastname,
sc.courseid,
sc.score,
DENSE_RANK() OVER (PARTITION BY sc.courseid ORDER BY sc.score DESC)
AS class_rank
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
ORDER BY
sc.courseid, class_rank;

Output 68:
firstname lastname courseid score class_rank
James Folkner 1 98 1
Ryan Brown 1 85 2
Cindy Brown 1 80 3
Michael Holden 1 62 4
Alison Cremette 1 59 5
Molly James 1 52 6
Cindy Brown 2 92 1
Julia Barklay 2 86 2
Michael Johnson 2 83 3
Cindy Brown 2 74 4
Molly James 3 69 1
Michael Johnson 3 58 2
Michael Holden 4 55 1
Cindy Brown 5 93 1
Alison Cremette 5 70 2
Michael Johnson 6 94 1
Julia Barklay 6 94 1
James Folkner 6 93 2
Michael Holden 6 57 3
Holly Michaels 6 55 4
Ryan Brown 6 55 4
Cindy Brown 6 54 5
James Folkner 7 77 1
Molly James 8 76 1
Julia Barklay 9 100 1
Molly James 9 70 2
John Clifford 9 65 3
Ryan Brown 10 81 1
Alison Cremette 11 63 1
John Clifford 12 75 1
Ann Williams 13 100 1
Holly Michaels 14 56 1
This query ranks students within each course based on their individual scores,
allowing you to identify the top-performing students in each course.
Let’s display the course name instead of the courseid.

SELECT
s.firstname,
s.lastname,
c.coursename,
sc.score,
DENSE_RANK() OVER (PARTITION BY c.coursename ORDER BY
sc.score DESC) AS class_rank
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
ORDER BY
c.coursename, class_rank;

Output 69:
firstname lastname coursename score class_rank
Molly James Assembly 76 1
Ann Williams C++ 100 1
Molly James C++ 69 2
Michael Johnson C++ 58 3
James Folkner CSS 77 1
Cindy Brown HTML 93 1
Alison Cremette HTML 70 2
Cindy Brown Java 92 1
Julia Barklay Java 86 2
Michael Johnson Java 83 3
John Clifford Java 75 4
Cindy Brown Java 74 5
Julia Barklay JavaScript 100 1
Molly James JavaScript 70 2
John Clifford JavaScript 65 3
Michael Johnson Pearl 94 1
Julia Barklay Pearl 94 1
James Folkner Pearl 93 2
Michael Holden Pearl 57 3
Holly Michaels Pearl 55 4
Ryan Brown Pearl 55 4
Cindy Brown Pearl 54 5
Michael Holden PHP 55 1
Ryan Brown Python 81 1
Alison Cremette Unix 63 1
James Folkner Visual Basic 98 1
Ryan Brown Visual Basic 85 2
Cindy Brown Visual Basic 80 3
Michael Holden Visual Basic 62 4
Alison Cremette Visual Basic 59 5
Holly Michaels Visual Basic 56 6
Molly James Visual Basic 52 7
These examples demonstrate the usage of RANK(), DENSE_RANK(), and
ROW_NUMBER() with your students and student_course tables. Changing the
partitioning and ordering criteria allows you to adapt these queries to suit your
specific analysis needs.
Finding the highest score achieved by each student
SELECT
s.studentid,
s.firstname,
s.lastname,
sc.score,
MAX(sc.score) OVER (PARTITION BY s.studentid) AS highest_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
ORDER BY
s.studentid;

Output 70:
studentid firstname lastname score highest_score
3 Michael Johnson 83 94
3 Michael Johnson 58 94
3 Michael Johnson 94 94
4 Molly James 76 76
4 Molly James 52 76
4 Molly James 69 76
4 Molly James 70 76
5 Holly Michaels 56 56
5 Holly Michaels 55 56
6 Cindy Brown 80 93
6 Cindy Brown 74 93
6 Cindy Brown 92 93
6 Cindy Brown 93 93
6 Cindy Brown 54 93
7 Julia Barklay 86 100
7 Julia Barklay 94 100
7 Julia Barklay 100 100
8 Alison Cremette 59 70
8 Alison Cremette 63 70
8 Alison Cremette 70 70
9 James Folkner 77 98
9 James Folkner 98 98
9 James Folkner 93 98
10 Michael Holden 57 62
10 Michael Holden 55 62
10 Michael Holden 62 62
11 Ryan Brown 81 85
11 Ryan Brown 55 85
11 Ryan Brown 85 85
12 John Clifford 65 75
12 John Clifford 75 75
13 Ann Williams 100 100
This query calculates the highest score achieved by each student across all their
courses.

Finding the lowest score received by each student


SELECT
s.studentid,
s.firstname,
s.lastname,
sc.score,
MIN(sc.score) OVER (PARTITION BY s.studentid) AS lowest_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
ORDER BY
s.studentid;

Output 71:
studentid firstname lastname score lowest_score
3 Michael Johnson 83 58
3 Michael Johnson 58 58
3 Michael Johnson 94 58
4 Molly James 76 52
4 Molly James 52 52
4 Molly James 69 52
4 Molly James 70 52
5 Holly Michaels 56 55
5 Holly Michaels 55 55
6 Cindy Brown 80 54
6 Cindy Brown 74 54
6 Cindy Brown 92 54
6 Cindy Brown 93 54
6 Cindy Brown 54 54
7 Julia Barklay 86 86
7 Julia Barklay 94 86
7 Julia Barklay 100 86
8 Alison Cremette 59 59
8 Alison Cremette 63 59
8 Alison Cremette 70 59
9 James Folkner 77 77
9 James Folkner 98 77
9 James Folkner 93 77
10 Michael Holden 57 55
10 Michael Holden 55 55
10 Michael Holden 62 55
11 Ryan Brown 81 55
11 Ryan Brown 55 55
11 Ryan Brown 85 55
12 John Clifford 65 65
12 John Clifford 75 65
13 Ann Williams 100 100
This query calculates the lowest score received by each student across all their
courses.
Identifying Students with Scores Above the Class Average in Multiple Courses
SELECT
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score,
AVG(sc.score) OVER (PARTITION BY sc.courseid) AS class_avg
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
sc.score > class_avg
GROUP BY
s.studentid;

Let’s run this query.


It produces an error:
ERROR 1055 (42000): Expression #4 of SELECT list is not in GROUP BY
clause and contains nonaggregated column 'mydb.sc.courseid' which is not
functionally dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by
mysql>
When we use a GROUP BY clause, we can only select columns explicitly
included in the GROUP BY clause. These are the columns used to group
the data.
Aggregate functions (SUM, AVG, MIN, MAX, and COUNT) are applied to other
columns.
In our query, we have GROUP BY s.studentid
In the SELECTclause, we have sc.score, which is not in a GROUP BY
clause. When using GROUP BY, you must either include all non-
aggregated columns in the GROUP BY clause or apply an aggregate
function (like SUM, AVG, MIN, MAX, COUNT) to the non-aggregated
columns.
Let’s include s.firstname, s.lastname, sc.courseid, and sc.score in the GROUP
BY clause.
SELECT
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score,
AVG(sc.score) OVER (PARTITION BY sc.courseid) AS class_avg
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
sc.score > (SELECT AVG(score) FROM student_course WHERE courseid =
sc.courseid)
GROUP BY
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score;
Run this corrected query.
ERROR 1054 (42S22): Unknown column 'class_avg' in 'where clause.'
The query produces this error because class_avg is the result of the window
function and cannot be included in the WHERE clause.
Correct query:

SELECT
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score,
AVG(sc.score) OVER (PARTITION BY sc.courseid) AS class_avg
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
sc.score > (SELECT AVG(score) FROM student_course WHERE courseid =
sc.courseid)
GROUP BY
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score;

Output 72:
studentid firstname lastname courseid score class_avg
9 James Folkner 1 98 87.6667
6 Cindy Brown 1 80 87.6667
11 Ryan Brown 1 85 87.6667
7 Julia Barklay 2 86 89.0000
6 Cindy Brown 2 92 89.0000
4 Molly James 3 69 69.0000
6 Cindy Brown 5 93 93.0000
3 Michael Johnson 6 94 93.6667
7 Julia Barklay 6 94 93.6667
9 James Folkner 6 93 93.6667
7 Julia Barklay 9 100 100.0000
Explanation:
The AVG(sc.score) OVER (PARTITION BY sc.courseid) AS class_avg
calculates the average score for each courseid and assigns it to the alias
class_avg. However, window functions cannot be directly used in the WHERE
clause.
We use a subquery within the WHERE clause to achieve the desired filtering.
This subquery is (SELECT AVG(score) FROM student_course WHERE
courseid = sc.courseid).
The subquery calculates the average score for the specific courseid of the
current row.
The WHERE clause compares the student's score with the average score
obtained from the subquery and includes only students whose score in the
course is greater than the average score for that course.
The GROUP BY clause includes the sc.score to ensure correct grouping when
selecting individual student scores and the calculated class average.
This corrected query successfully identifies students whose scores exceed the
class average for their respective courses.
Let’s find students with scores below the class average for their respective
courses. It is easy to do. Just change the greater than sign to less than.
SELECT
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score,
AVG(sc.score) OVER (PARTITION BY sc.courseid) AS class_avg
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
WHERE
sc.score < (SELECT AVG(score) FROM student_course WHERE courseid =
sc.courseid)
GROUP BY
s.studentid,
s.firstname,
s.lastname,
sc.courseid,
sc.score;

Output 73:
firstname lastname courseid score class_avg
studentid
8 Alison Cremette 1 59 57.6667
4 Molly James 1 52 57.6667
10 Michael Holden 1 62 57.6667
6 Cindy Brown 2 74 78.5000
3 Michael Johnson 2 83 78.5000
3 Michael Johnson 3 58 58.0000
8 Alison Cremette 5 70 70.0000
10 Michael Holden 6 57 55.2500
6 Cindy Brown 6 54 55.2500
5 Holly Michaels 6 55 55.2500
11 Ryan Brown 6 55 55.2500
12 John Clifford 9 65 67.5000
4 Molly James 9 70 67.5000

CONDITIONAL LOGIC WITH CASE


Let's explore some CASE examples using our database.
SELECT
coursename,
hours,
CASE
WHEN hours >= 500 THEN 'Long'
WHEN hours > 200 AND hours < 500 THEN 'Moderate'
ELSE 'Short'
END AS course_duration
FROM
courses
Order by hours desc;

Output 74:

coursename hours AVG(c.hours) course_duration


Unix 700 700.0000 Long
C++ 600 600.0000 Long
Java 500 500.0000 Long
C# 500 500.0000 Long
CSS 400 400.0000 Moderate
Assembly 400 400.0000 Moderate
Perl 400 400.0000 Moderate
Visual Basic 360 360.0000 Moderate
PHP 300 300.0000 Moderate
Pearl 300 300.0000 Moderate
Python 300 300.0000 Moderate
HTML 200 200.0000 Short
JavaScript 200 200.0000 Short
In the query above, the CASE Statement categorizes the hours of the course
into three categories: Long if the hours are 500 or more, Moderate if the hours
are greater than 200 but less than 500, and Short if the hours are 200 or less.
Let’s build a query that selects the coursename and cost from the courses table
and categorizes the cost into three categories: Expensive, Moderate, and Not
Expensive.
SELECT
coursename,
cost,
CASE
WHEN cost >= 2000 THEN 'Expensive'
WHEN cost > 1000 AND cost < 2000 THEN 'Moderate'
ELSE 'Not Expensive'
END AS cost_rate
FROM
courses
GROUP BY coursename, cost
Order by cost desc;

Output 75:

coursename cost cost_rate


Java 2999.95 Expensive
C++ 2999.95 Expensive
C# 2999.95 Expensive
Unix 2999.95 Expensive
Visual Basic 1999.95 Moderate
Assembly 1999.95 Moderate
Pearl 1699.95 Moderate
Assembly 1699.95 Moderate
Unix 1000 Not Expensive
PHP 999.95 Not Expensive
JavaScript 999.95 Not Expensive
Python 999.95 Not Expensive
Perl 999.95 Not Expensive
CSS 899.95 Not Expensive
HTML 699.95 Not Expensive
In the query above, the CASE statement creates a new column named
cost_rate to categorize the course cost.

The next query assigns student letter scores (A, B, C, D, F) based on their
numerical scores.
SELECT
s.firstname,
s.lastname,
sc.score,
CASE
WHEN sc.score >= 90 THEN 'A'
WHEN sc.score >= 80 THEN 'B'
WHEN sc.score >= 70 THEN 'C'
WHEN sc.score >= 60 THEN 'D'
ELSE 'F'
END AS grade
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid;

Output 76:
firstname lastname score grade
John Clifford 65 D
Molly James 76 C
James Folkner 77 C
Michael Holden 57 F
Michael Holden 55 F
Julia Barklay 86 B
James Folkner 98 A
Ryan Brown 81 B
Alison Cremette 59 F
Holly Michaels 56 F
Molly James 52 F
Michael Holden 62 D
Cindy Brown 80 B
Alison Cremette 63 D
Cindy Brown 74 C
Michael Johnson 83 B
Cindy Brown 92 A
Michael Johnson 58 F
Molly James 69 D
Alison Cremette 70 C
Cindy Brown 93 A
Cindy Brown 54 F
Michael Johnson 94 A
Holly Michaels 55 F
Julia Barklay 94 A
Ryan Brown 55 F
James Folkner 93 A
Julia Barklay 100 A
Molly James 70 C
Ryan Brown 85 B
John Clifford 75 C
Ann Williams 100 A
Let’s build a query that calculates the difference between the maximum scores
of the morning and afternoon sessions for the same course.
SELECT
s.firstname,
s.lastname,
c.coursename as course,
MAX(CASE WHEN sh.starttime BETWEEN '08:00:00' AND '12:00:00' THEN
sc.score END) AS AM_Score,
MAX(CASE WHEN sh.starttime BETWEEN '12:00:00' AND '17:00:00' THEN
sc.score END) AS PM_Score,
MAX(CASE WHEN sh.starttime BETWEEN '08:00:00' AND '12:00:00'
THEN sc.score END) -
MAX(CASE WHEN sh.starttime BETWEEN '12:00:00' AND '17:00:00'
THEN sc.score END) AS difference
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
INNER JOIN courses c ON sc.courseid = c.courseid
INNER JOIN schedule sh ON c.courseid = sh.courseid
GROUP BY
s.firstname, s.lastname, c.coursename
HAVING COUNT(*) > 1;

Output 77:

firstname lastname course AM_Score PM_Score difference


Cindy Brown Java 92 NULL NULL
John Clifford Java NULL 75 NULL

Increase teacher rate based on student performance.


SELECT
t.teacherid,
t.firstname,
t.lastname,
CASE
WHEN AVG(sc.score) >= 90 THEN t.rate * 1.30
WHEN AVG(sc.score) >= 80 THEN t.rate * 1.15
WHEN AVG(sc.score) >= 70 THEN t.rate * 1.10
ELSE t.rate
END AS new_rate
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname;

Output 78:
teacherid firstname lastname rate new_rate
8 Greg Gerald 30 33.00
2 John Jefferson 51 58.65
5 Julia Williams 51 56.10
4 Michael Murphy 61 67.10
10 Michael Kelly 25 25
9 Michael Ross 20 22.00
6 John Niven 20 23.00
3 James Barry 51 51
1 John Smith 62 62
Explanation:
The SELECT clause retrieves the teacher's ID, first name, and last name.
CASE statement calculates a teacher's new rate based on the average score of
the students taught by each teacher.
The teacher rate increased by 30% if the average student score is 90 or more.
The teacher rate is increased by 15% if the average student score is between 80
and 90, and the rate is increased by 10% if the average student score is between
70 and 80.
If the score is less than 70, the teacher remains the same.

STORED PROCEDURES
Get Student Grades for a Specific Course.
DELIMITER $$
CREATE PROCEDURE GetStudentGradesForCourse(IN p_courseid INT)
BEGIN
SELECT
s.studentid,
s.firstname,
s.lastname,
sc.score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.courseid = p_courseid;
END $$
DELIMITER ;

mysql> call GetStudentGradesForCourse(7);


Output 79:
studentid firstname lastname score
9 James Folkner 77
Explanation:
The "DELIMITER $$" command changes the default statement delimiter from
; to $$.
It allows the use of ; within the procedure body without ending the procedure
definition prematurely.
The "CREATE PROCEDURE" command creates a new stored procedure
named GetStudentGradesForCourse.
The "IN p_courseid INT" defines an input parameter named p_courseid of type
INT. It is used to pass the course ID to the procedure.
The "BEGIN ... END" block defines the body of the stored procedure,
containing the SQL statements to be executed.
The "END $$" command marks the end of the procedure definition.
The "DELIMITER ;" command resets the statement delimiter back to the
default ';'.
This stored procedure, GetStudentGradesForCourse, retrieves a list of students
and their scores for a specified course. The course ID is passed as an input
parameter (p_courseid). The procedure joins the students, student_course, and
courses tables to fetch the necessary data and filter the results based on the
provided course ID.
To call this stored procedure for a course with ID 7, you would use:
CALL GetStudentGradesForCourse(7);
To delete a stored procedure, use the following SQL statement:

Drop GetStudentGradesForCourse;
To create a stored procedure for MySQL, copy and paste its beginning and end
parts and put the desired query between them.

DELIMITER $$
CREATE PROCEDURE stored_procedure_name( parameters)
BEGIN
<---------- put your query here----------------------------------- >
END $$
DELIMITER ;
You can use different characters as the delimiter in MySQL. The delimiter
defines the end of a statement, especially when working with stored procedures,
triggers, or other complex SQL scripts. By default, the delimiter is a semicolon
(;), but when you're writing multi-statement scripts, changing the delimiter is
common to avoid conflicts with the semicolons used within the statements.
For example, you can use // or $$ or any other character string as the delimiter.
Let’s take this query and create a stored procedure from it.
SELECT
s.firstname AS st_lastname,
s.lastname AS st_firstname,
t.firstname AS t_firstname,
t.lastname AS t_lastname,
c.coursename
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
c.coursename = 'C++'
AND sch.starttime >= '00:00:00'
AND sch.starttime < '12:00:00';

Output 80:
st_lastname st_firstname t_firstname t_lastname coursename
Michael Johnson John Smith C++
Molly James John Smith C++
Let’s name this procedure GetMorningStudentsByCourse()
This procedure required one parameter coursename.
DELIMITER $$

CREATE PROCEDURE GetMorningStudentsByCourse(IN p_course_name


VARCHAR(50))
BEGIN
SELECT
s.firstname AS st_lastname,
s.lastname AS st_firstname,
t.firstname AS t_firstname,
t.lastname AS t_lastname,
c.coursename AS course
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
c.coursename = p_course_name
AND sch.starttime >= '00:00:00'
AND sch.starttime < '12:00:00';
END $$
DELIMITER ;

Call GetMorningStudentsByCourse('C++');
Output 81:
st_lastname st_firstname t_firstname t_lastname coursename
Michael Johnson John Smith C++
Molly James John Smith C++
Call GetMorningStudentsByCourse('PHP');

Output 82:
st_lastname st_firstname t_firstname t_lastname coursename
Michael Holden Michael Kelly PHP
Let’s create a stored procedure that gets teachers and students for any course
regardless of the schedule.
DELIMITER $$
CREATE PROCEDURE GetStudentsTeachersByCourse(IN p_course_name
VARCHAR(50))
BEGIN
SELECT
s.firstname AS st_lastname,
s.lastname AS st_firstname,
t.firstname AS t_firstname,
t.lastname AS t_lastname,
c.coursename AS course
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
c.coursename = p_course_name;
END $$
DELIMITER ;

Call GetStudentsTeachersByCourse('C++');
Output 83:

st_lastname st_firstname t_firstname t_lastname course


Michael Johnson John Smith C++
Molly James John Smith C++
Ann Williams John Jefferson C++
Call GetStudentsTeachersByCourse('Java');

Output 84:
st_lastname st_firstname t_firstname t_lastname course
Julia Barklay John Jefferson Java
Cindy Brown John Jefferson Java
Michael Johnson John Jefferson Java
Cindy Brown John Jefferson Java
John Clifford John Jefferson Java
John Clifford John Jefferson Java
Let’s add an end-time parameter to our procedure.
DELIMITER $$

CREATE PROCEDURE GetStudentsTeachersByCourseMod(IN p_course_name


VARCHAR(50), IN p_end_time TIME)
BEGIN
SELECT
s.firstname AS st_lastname,
s.lastname AS st_firstname,
t.firstname AS t_firstname,
t.lastname AS t_lastname,
c.coursename AS course
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
c.coursename = p_course_name
AND sch.endtime <= p_end_time;
END $$

DELIMITER ;

CALL GetStudentsTeachersByCourseMod('HTML', '12:00:00');

Output 85:
st_lastname st_firstname t_firstname t_lastname course
Alison Cremette Michael Murphy HTML
Cindy Brown Michael Murphy HTML
Create a stored procedure that finds students with scores above the class
average.
DELIMITER $$
CREATE PROCEDURE studentsAboveAverage()
BEGIN

SELECT
s.studentid,
s.firstname,
s.lastname,
c.coursename,
MIN(sc.score) AS sample_score
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN (
SELECT
sc.courseid,
AVG(sc.score) AS class_average
FROM
student_course sc
GROUP BY
sc.courseid

) AS avg_grades ON sc.courseid = avg_grades.courseid

WHERE
sc.score > avg_grades.class_average
GROUP BY
s.studentid,
s.firstname,
s.lastname,
c.coursename;

END $$
DELIMITER ;

Call studentsAboveAverage();
Output 86:
studentid firstname lastname coursename sample_score
7 Julia Barklay Java 86
9 James Folkner Visual Basic 98
6 Cindy Brown Visual Basic 80
6 Cindy Brown Java 92
4 Molly James C++ 69
6 Cindy Brown HTML 93
3 Michael Johnson Pearl 94
7 Julia Barklay Pearl 94
9 James Folkner Pearl 93
7 Julia Barklay JavaScript 100
11 Ryan Brown Visual Basic 85
Let's create a stored procedure to display morning courses.
DELIMITER $$

CREATE PROCEDURE GetMorningCourses()


BEGIN

SELECT
c.coursename,
c.hours,
c.cost,
s.starttime
FROM
courses c
JOIN
schedule s ON c.courseid = s.courseid
WHERE
TIME(s.starttime) >= '08:00:00'
AND TIME(s.starttime) <= '12:00:00';
END $$

DELIMITER ;

Call GetMorningCourses();
Output 87:
coursename hours cost starttime
C++ 550 3999.95 9:00:00
HTML 200 699.95 8:00:00
CSS 400 899.95 12:00:00
Pearl 300 1699.95 8:00:00
Assembly 400 1699.95 8:00:00
Visual Basic 360 1999.95 9:00:00
Assembly 400 1999.95 12:00:00
Java 500 2999.95 9:00:00
Python 300 999.95 9:00:00
PHP 300 999.95 9:00:00
JavaScript 200 999.95 8:00:00
Find a student with the highest score in a course.
DELIMITER $$

CREATE PROCEDURE StudentWithHighestScore(IN p_course_name VARCHAR(50))


BEGIN
SELECT
s.firstname,
s.lastname,
c.coursename,
sc.score
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
INNER JOIN courses c ON sc.courseid = c.courseid
WHERE
c.coursename = p_course_name
ORDER BY
sc.score DESC
LIMIT 1;
END $$

DELIMITER ;

call StudentWithHighestScore('Java');
Output 88a:

firstname lastname coursename score


Julia Barklay JavaScript 100

call StudentWithHighestScore('CSS');
Output 88b:
firstname lastname coursename score
James Folkner CSS 77
call StudentWithHighestScore('JavaScript');
Output 88c:
firstname lastname coursename score
Julia Barklay JavaScript 100
Find a student's average score.
DELIMITER $$

CREATE PROCEDURE StudentAvgScore(IN p_lname VARCHAR(50))


BEGIN
SELECT
s.firstname,
s.lastname,
AVG(sc.score) AS average_score
FROM
students s
INNER JOIN student_course sc ON s.studentid = sc.studentid
WHERE s.lastname=p_lname
GROUP BY
s.firstname,
s.lastname;
END $$

DELIMITER ;

Call StudentAvgScore('Barklay');

Output 89a:
firstname lastname average_score
Julia Barklay 93.3333
Call StudentAvgScore('Holden');
Output 89b:
firstname lastname average_score
Michael Holden 58.0000
Call StudentAvgScore('Johnson');
Output 89c:
firstname lastname average_score
Michael Johnson 78.3333
Create a procedure for a teacher schedule.
DELIMITER $$

CREATE PROCEDURE getTeacherSchedule(IN p_lname VARCHAR(50))


BEGIN

SELECT
t.firstname,
t.lastname,
s.starttime,
s.endtime,
c.coursename
FROM
schedule s
JOIN
courses c ON s.courseid = c.courseid
JOIN
teachers t ON s.teacherid = t.teacherid
WHERE
t.lastname = p_lname;

END $$

DELIMITER ;

Call getTeacherSchedule('Jefferson');
Output 90a:
firstname lastname starttime endtime coursename
John Jefferson 09:00:00 15:00:00 Java
John Jefferson 18:00:00 22:00:00 C++
John Jefferson 18:00:00 22:00:00 Java
John Jefferson 14:00:00 18:00:00 Java
Call getTeacherSchedule('Kelly');
Output 90b:
firstname lastname starttime endtime coursename
Michael Kelly 09:00:00 12:00:00 PHP
Create the procedure for a student schedule.
DELIMITER $$

CREATE PROCEDURE getStudentSchedule(IN p_lname VARCHAR(50))


BEGIN

SELECT
s.firstname,
s.lastname,
c.coursename,
sch.starttime,
sch.endtime
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
JOIN
schedule sch ON c.courseid = sch.courseid
WHERE
s.lastname = p_lname;

END $$

DELIMITER ;

Call getStudentSchedule('Holden');
Output 91:
firstname lastname coursename starttime endtime
Michael Holden Visual Basic 18:00:00 21:00:00
Michael Holden Pearl 08:00:00 11:00:00
Michael Holden PHP 09:00:00 12:00:00

STORED FUNCTIONS
MySQL functions have a slightly different structure compared to stored
procedures. Specifically, you need to declare the variable before the BEGIN
block.
Let’s create a function that accepts a text string and returns unique characters
from the string.
DELIMITER $$

CREATE FUNCTION f_getUniqueCharacters(input_text VARCHAR(255))


RETURNS VARCHAR(255)
DETERMINISTIC
BEGIN
DECLARE unique_chars VARCHAR(255) DEFAULT ' ';
DECLARE current_char CHAR(1);
DECLARE idx INT DEFAULT 1;

WHILE idx <= LENGTH(input_text) DO


SET current_char = SUBSTRING(input_text, idx, 1);
IF LOCATE(current_char, unique_chars) = 0 THEN
SET unique_chars = CONCAT(unique_chars, current_char);
END IF;
SET idx = idx + 1;
END WHILE;

RETURN unique_chars;
END $$

DELIMITER ;

The DETERMINISTIC keyword is used to specify that the function will return
the same result if the input values are the same.
Variables are declared inside the BEGIN block.
You can call a function within a query.
SELECT f_getUniqueCharacters(lastname) as unique_chars from teachers where
lastname='Jefferson';

output 91a:

Unique_chars
Jefrson
SELECT f_getUniqueCharacters(lastname) as unique_chars from teachers where
lastname='Williams';

Output 91b:
unique_chars
Wilams
Let’s take a query that converts scores to grades and create a function.
Here is the query:
SELECT
s.firstname,
s.lastname,
sc.score,
CASE
WHEN sc.score >= 90 THEN 'A'
WHEN sc.score >= 80 THEN 'B'
WHEN sc.score >= 70 THEN 'C'
WHEN sc.score >= 60 THEN 'D'
ELSE 'F'
END AS grade
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid;

Below is the function. It accepts a student's score and converts it to the grade.
It is important to understand that a function can return only one value.
It means that the query inside the function must return only one row.
DELIMITER $$

CREATE FUNCTION getGrade(score INT)


RETURNS CHAR(1)
DETERMINISTIC
BEGIN
DECLARE grade CHAR(1);

IF score >= 90 THEN


SET grade = 'A';
ELSEIF score >= 80 THEN
SET grade = 'B';
ELSEIF score >= 70 THEN
SET grade = 'C';
ELSEIF score >= 60 THEN
SET grade = 'D';
ELSE
SET grade = 'F';
END IF;

RETURN grade;
END $$

DELIMITER ;

Let’s call function.


SELECT getGrade(score) as Grade FROM student_course where
courseid=14 and studentid=5;
Output 92a:
Grade
F
SELECT getGrade(score) as Grade FROM student_course where courseid=6 and
studentid=3;

Output 92b:
Grade
A
SELECT getGrade(score) as Grade FROM student_course where courseid=13 and
studentid=13;

Output 92c:
Grade
A
SELECT getGrade(score) as Grade FROM student_course where courseid=7
and studentid=9;
Output 92d:
Grade
C
Let’s create a function that takes a date of birth and returns the age.
DELIMITER $$

CREATE FUNCTION getAge(date_of_birth DATE)


RETURNS INT
DETERMINISTIC
BEGIN
DECLARE age INT;

SET age = TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE());

RETURN age;
END $$
DELIMITER ;

Let’s call this function.


SELECT getAge('02/01/20') ;
Output 94a:
getAge('02/01/20')
23
SELECT getAge(dob) from students where firstname='Michael'

Output 94b:
getAge(dob)
25
23
The TIMESTAMPDIFF function in SQL calculates the difference between two
timestamps. It returns the difference as an integer value based on a specified
unit of time. This function is commonly used to measure the duration between
two dates or times.
TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)
You can use SECOND, MINUTE, HOUR, DAY, WEEK, MONTH,
QUARTER, and YEAR for the function unit parameter.
You can modify the getAge function to return age in days.

DELIMITER $$

CREATE FUNCTION getAgeInDays(date_of_birth DATE)


RETURNS INT
DETERMINISTIC
BEGIN
DECLARE age INT;

SET age = TIMESTAMPDIFF(DAY, date_of_birth, CURDATE());

RETURN age;
END $$

DELIMITER ;

SELECT getAgeInDays('02/01/20') as Age_In_Days ;


Age_In_Days
8419
Let’s create a function that converts MySQL date format to human date format.

DELIMITER //

CREATE FUNCTION format_mysql_date (p_date DATE)


RETURNS VARCHAR(20)
DETERMINISTIC -- Important: Add DETERMINISTIC or READS SQL DATA if
needed
BEGIN
DECLARE v_formatted_date VARCHAR(20);

IF p_date IS NULL THEN


SET v_formatted_date = '';
ELSE
SET v_formatted_date = DATE_FORMAT(p_date, '%m/%d/%y');
END IF;

RETURN v_formatted_date;
END //

DELIMITER ;

SELECT format_mysql_date('2025-01-31');
Output 95a:
format_mysql_date('2025-01-31')
01/31/25
SELECT dob, format_mysql_date(dob) as hdob from students where
lastname='James';
Output 95b:

lastname dob hdob


James 2003-07-13 07/13/03
SELECT firstname, lastname, format_mysql_date(dob) as dob from students
where lastname='Barklay';

Output 95c:
firstname lastname dob
Julia Barklay 01/25/01
The DATE_FORMAT function in SQL formats a date value according to a
specified format string.
This function formats a date value based on a specified format string.
The p_date is the date value you want to format.
The '%m/%d/%y' is the format string that specifies how the date should be
formatted.
The format string consists of format specifiers:
The %m stays for the month as a zero-padded number (01-12).
The %d stays for days of the month as a zero-padded number (01-31).
The %y stays for a year as a two-digit number (00-99).
Let’s create a function that converts a “human” date to a MySQL date.
DELIMITER //

CREATE FUNCTION convert_human_to_mysql_date (p_human_date


VARCHAR(20))
RETURNS DATE
DETERMINISTIC
BEGIN
DECLARE v_mysql_date DATE;

IF p_human_date IS NULL THEN


RETURN NULL;
END IF;

-- Attempt conversion; if it fails, return NULL


SET v_mysql_date = STR_TO_DATE(p_human_date, '%m/%d/%y'); -- Note
the format string

RETURN v_mysql_date;
END //

DELIMITER ;

The line "STR_TO_DATE(p_human_date, '%m/%d/%y')" attempts to


convert the p_human_date string into a MySQL date using the
STR_TO_DATE function with the format '%m/%d/%y'.
The format string specifies that the input date is in the format MM/DD/YY.
This function may be used in the insert statement.
Let’s try this function with an incorrect date format.
mysql> SELECT convert_human_to_mysql_date('31/01/25');
ERROR 1411 (HY000): Incorrect datetime value: '31/01/25' for function
str_to_date
As expected, we got an error because a year includes only 12 months, not 31.
SELECT convert_human_to_mysql_date(NULL) as MySQL_Date;
Output 96a:

MySQL_Date
NULL
SELECT convert_human_to_mysql_date('01/31/25') as MySQL_Date;
Output 96b:
MySQL_Date

2025-01-31
Let’s try to use an empty string for the date value:
SELECT convert_human_to_mysql_date('') as MySQL_Date;
ERROR 1411 (HY000): Incorrect datetime value: '' for function str_to_date
We can modify our function to handle this error.

DELIMITER //

CREATE FUNCTION convert_human_to_mysql_datemod (p_human_date


VARCHAR(20))
RETURNS DATE
DETERMINISTIC
BEGIN
DECLARE v_mysql_date DATE;

IF p_human_date IS NULL THEN


RETURN NULL;
END IF;

IF p_human_date ='' THEN


RETURN NULL;
END IF;

-- Attempt conversion; if it fails, return NULL


SET v_mysql_date = STR_TO_DATE(p_human_date, '%m/%d/%y'); -- Note
the format string

RETURN v_mysql_date;
END //

DELIMITER ;

Now we get:

SELECT convert_human_to_mysql_datemod('') as
MySQL_Date;
Output 96c:
MySQL_Date

NULL
Let’s create a function that calculates the average score for a course. This
function inputs the course name and returns the average student score.
DELIMITER //

CREATE FUNCTION average_student_score(p_coursename VARCHAR(255))


RETURNS DECIMAL(5, 2)
DETERMINISTIC -- Or READS SQL DATA
BEGIN
DECLARE v_average_score DECIMAL(5, 2);
DECLARE v_course_id INT;

-- Get the course ID first (important for efficiency and correctness)


SELECT courseid INTO v_course_id
FROM courses
WHERE coursename = p_coursename;

-- If the course isn't found, return 0 (or NULL)


IF v_course_id IS NULL THEN
RETURN 0.00; -- Or NULL if you prefer
END IF;

-- Now calculate the average score using the course ID


SELECT AVG(score) INTO v_average_score
FROM student_course
WHERE courseid = v_course_id;

-- Handle the case where no students are enrolled in the course


IF v_average_score IS NULL THEN
SET v_average_score = 0.00; -- Or NULL
END IF;

RETURN v_average_score;
END //

DELIMITER ;

Let's run this function.


SELECT average_student_score('C++');
ERROR 1172 (42000): Result consisted of more than one row
An error has occurred. Why do you think we got an error?
We got an error because the get courseid query inside the function returns two
IDs: one for the morning C++ course and one for the afternoon C++ course.
We can modify our function by adding one more input parameter:
p_coursetime. We agree that this parameter is 1 for morning and 2 for
afternoon courses.
Before modifying this function, we must drop it and create a new one.
mysql> drop function average_student_score;
Query OK, 0 rows affected (0.01 sec)
Create a new version of the function.
DELIMITER //

CREATE FUNCTION average_student_score(p_coursename VARCHAR(255),


p_coursetime INT) -- Added coursetime parameter
RETURNS DECIMAL(5, 2)
DETERMINISTIC
BEGIN
DECLARE v_average_score DECIMAL(5, 2);
DECLARE v_course_id INT;
DECLARE v_starttime TIME; -- To store the start time

-- Get the course ID and starttime based on coursename and coursetime


SELECT c.courseid, s.starttime INTO v_course_id, v_starttime
FROM courses c
JOIN schedule s ON c.courseid = s.courseid
WHERE c.c.coursename = p_coursename
AND CASE -- Handle morning (1) or afternoon (2)
WHEN p_coursetime = 1 THEN TIME(s.starttime) < TIME('12:00:00') --
Morning
WHEN p_coursetime = 2 THEN TIME(s.starttime) >= TIME('12:00:00') --
Afternoon
ELSE TRUE -- Handle invalid input (optional: you could return an error
here)
END;

-- If the course isn't found, return 0 (or NULL)


IF v_course_id IS NULL THEN
RETURN 0.00; -- Or NULL
END IF;

-- Calculate the average score


SELECT AVG(sc.score) INTO v_average_score
FROM student_course sc
WHERE sc.courseid = v_course_id;

-- Handle the case where no students are enrolled in the course


IF v_average_score IS NULL THEN
SET v_average_score = 0.00; -- Or NULL
END IF;

RETURN v_average_score;
END //

DELIMITER ;

Let’s call our function again.


SELECT average_student_score('C++', 1) as avg_score;
Output 97a:
avg_score

63.50
SELECT average_student_score('C++', 2) as avg_score;
Output 97b:
avg_score

100.00
HOMEWORK
Please write queries to select the following data.
1. Find the number of students for each teacher.

firstname lastname num_of_students


Julia Williams 8
Michael Ross 7
John Jefferson 6
Michael Murphy 3
Greg Gerald 3
John Smith 2
James Barry 1
John Niven 1
Michael Kelly 1
2. Find the teacher who teaches the most students
firstname lastname num_of_students
Julia Williams 8
3. List all courses taught by a specific teacher (e.g., ‘Williams’)

coursename lastname firstname


Pearl Williams Julia
Assembly Williams Julia
Assembly Williams Julia
Python Williams Julia
Unix Williams Julia
Pearl Williams Julia
C# Williams Julia
4. Find the average score for each course

JavaScript 78
Java 83
Assembly 76
CSS 77
Pearl 72
PHP 55
Visual Basic 73
Python 81
Unix 63
C++ 76
HTML 82
5. List all students who have taken a specific course (e.g., 'JavaScript')

firstname lastname coursename


John Clifford JavaScript
Julia Barklay JavaScript
Molly James JavaScript
6. Find the total number of students enrolled in each course
coursename num_students
Assembly 1
C++ 3
CSS 1
HTML 2
Java 5
JavaScript 3
Pearl 7
PHP 1
Python 1
Unix 1
Visual Basic 8
7. Find the teacher with the highest average student score across all their courses
firstname lastname ROUND(AVG(sc.score), 2 )
John Jefferson 83.75
8. Total revenue generated by all courses:
total_revenue
41598.85
9 Calculate the cost of all courses taken by a specific student.( studentid=6)

lastname firstname total_spent


Brown Cindy 10399.75
10. Total amount spent by a specific student (e.g., student with ID = 6)
total_spent
8699.8
11. Total revenue generated by a specific course (e.g., 'C++')

coursename course_revenue
C++ 5999.9
2. Total amount spent by all students on a specific course (e.g., 'Visual Basic')
coursename total_student_spending
Visual Basic 13999.65
13. Average amount spent per student across all courses

lastname firstname avg_student_spending


Barklay Julia 1899.95
Brown Cindy 2079.95
Brown Ryan 1566.62
Clifford John 1999.95
Cremette Alison 1233.3
Folkner James 1533.28
Holden Michael 1566.62
James Molly 1924.95
Johnson Michael 2566.62
Michaels Holly 1849.95
Williams Ann 2999.95
14. Total commission for all teachers (Assuming a 10% commission for teachers
)
total_teacher_earnings
6489.83
15. Find the course with the highest total revenue

coursename total_revenue
Java 17999.7
16. Find the student who has spent the most on courses

studentid firstname lastname total_spent


6 Cindy Brown 8699.8
17. Find the average cost of all courses
avg_course_cost
1733.29
18. Find the most expensive course.
coursename cost
Java 2999.95
C++ 2999.95
Java 2999.95
C++ 2999.95
C# 2999.95
Unix 2999.95
C# 2999.95
19. Find the least expensive course.
coursename cost
HTML 699.95
HTML 699.95
20. Find the total revenue generated by each teacher (Assuming a 10%
commission for teachers).
firstname lastname teacher_revenue
Greg Gerald 299.99
John Jefferson 2399.96
Julia Williams 1359.96
Michael Murphy 329.99
Michael Kelly 100
Michael Ross 1399.96
John Niven 100
James Barry 199.99
John Smith 599.99
21. Find the average student spending per course

coursename avg_student_spending_per_course
Assembly 1699.95
C++ 2999.95
CSS 899.95
HTML 699.95
Java 3599.94
JavaScript 999.95
Pearl 1699.95
PHP 999.95
Python 999.95
Unix 1000
Visual Basic 1999.95
You can find solutions on page 135.

Create stored Procedures


1. Create a Stored Procedure that finds the number of students for each teacher.
Call GetStudentNumber();

firstname lastname num_of_students


Julia Williams 8
Michael Ross 7
John Jefferson 6
Michael Murphy 3
Greg Gerald 3
John Smith 2
James Barry 1
John Niven 1
Michael Kelly 1
2. Create a Stored Procedure that lists all courses a specific teacher teaches. Use
a teacher’s lastname as an input parameter.
Call getTeacherCourses('Jefferson');
coursename lastname firstname
Java Jefferson John
C++ Jefferson John
Java Jefferson John
Java Jefferson John

3. Create a Stored Procedure that finds the teacher who teaches the most
students
Call TeacherWithMostStudents();
firstname lastname num_of_students
Julia Williams 8

4. Create a Stored Procedure that lists all students who have taken a specific
course.
call getStudentsForCourses('C++');

firstname lastname highest_score


John Jefferson 83.75

5. Create a Stored Procedure that finds the teacher with the highest average
student score across all their courses
Call getTeacherWithHighestStudentScore ();

firstname lastname highest_score


John Jefferson 83.75

6. Create a Stored Procedure that calculates the cost of a specific student's


courses. The input parameter is a student's lastname.

Call getCoursesCostPerStudent('Brown');

firstname lastname total_spent


Ryan Brown 4699.85
Cindy Brown 10399.75

7. Create a Stored Procedure that calculates the total amount spent by a specific
student. (The input parameter is a student's lastname.)

Call getStudentAmountSpent('Brown');

firstname lastname total_spent


Ryan Brown 2999.9
Cindy Brown 8699.8

8. Create a Stored Procedure that calculates the total amount all students spend
on a specific course.
Call getAmountSpentPerCourse('Java');

coursename total_student_spending
Java 14999.75

9. Create a Stored Procedure that finds the least expensive course


Call getLeastExpensiveCourse();
coursename cost
HTML 699.95
HTML 699.95
10. Find the average cost of all courses
Call getAvgCoursesCost();
avg_course_cost
1733.29
You can find solutions on page 135.

Create stored functions


1. Create a function that returns the number of characters in a string.
SELECT lastname, get_string_length(lastname) as name_length FROM
Teachers order by name_length;
lastname name_length
Ross 4
Smith 5
Barry 5
Niven 5
Merry 5
Kelly 5
Karon 5
Murphy 6
Gerald 6
Williams 8
Jefferson 9

2. Create a function that calculates the average cost of all courses.


SELECT get_avg_courses_cost();
get_avg_courses_cost()
1733.29
SELECT get_avg_courses_cost() as Avg_Courses_Cost;
Avg_Courses_Cost
1733.29
3. Create a function that calculates the total amount spent by a specific student.
The input parameter is a student's lastname.
SELECT get_student_amount_spent('Brown');
get_student_amount_spent('Brown')
11699.70
SELECT get_student_amount_spent('Brown') as student_amount_spent;
student_amount_spent
11699.70
4. Calculate the total cost of all courses.
SELECT get_total_courses_cost();
get_total_courses_cost()
41598.85
SELECT get_total_courses_cost() as total_courses_cost;
total_courses_cost
41598.85
5. Create a function that returns the number of students in a specific course.
SELECT get_number_of_students_in_course('C++');
get_number_of_students_in_course('C++')
3
SELECT get_number_of_students_in_course('C++') as student_number;
student_number
3
6. Create a function that returns the average student score across all courses.
SELECT get_average_student_score_all_courses() as Avg_Score;
Avg_Score
75.29
7. Function to get the cost of a specific course

SELECT get_course_cost('C++') as course_cost;


course_cost
2999.95
8. Create a function that finds a teacher with the highest average student score
across all their courses
SELECT get_teacher_with_highest_student_score();
get_teacher_with_highest_student_score()
John Jefferson
9. Create a function that finds the teacher with the lowest average student score
across all their courses
SELECT get_teacher_with_lowest_student_score();
get_teacher_with_lowest_student_score()
Michael Kelly
10 Create a function that calculates the average score difference between
morning and afternoon sections of the same course.
SELECT get_average_score_diff('C++') as moning_afternoon_diff;
moning_afternoon_diff
-36.50
SELECT get_average_score_diff('Java') as moning_afternoon_diff;
moning_afternoon_diff

9.00
HOMEWORK SOLUTIONS

Queries from the homework


1. Find the number of students for each teacher.
SELECT
t.firstname,
t.lastname,
COUNT(DISTINCT sc.studentid) as num_of_students
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname
ORDER BY
COUNT(DISTINCT sc.studentid) DESC;

2. Find the teacher who teaches the most students


SELECT
t.firstname,
t.lastname,
COUNT(DISTINCT sc.studentid) as num_of_students
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname
ORDER BY
COUNT(DISTINCT sc.studentid) DESC
LIMIT 1;

3. List all courses taught by a specific teacher (e.g., 'Williams')


SELECT
c.coursename,
t.lastname,
t.firstname
FROM
courses c
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
t.firstname = 'Julia' AND t.lastname = 'Williams';

4. Find the average score for each course


SELECT
c.coursename,
ROUND(AVG(sc.score)) AS avg_score
FROM
courses c
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
c.coursename;

5. List all students who have taken a specific course (e.g., 'JavaScript')
SELECT
s.firstname,
s.lastname,
c.coursename
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = 'JavaScript';

6. Find the total number of students enrolled in each course.


SELECT
c.coursename,
COUNT(DISTINCT sc.studentid) AS num_students
FROM
courses c
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
c.coursename;
7. Find the teacher with the highest average student score across all their courses

SELECT
t.firstname,
t.lastname,
ROUND(AVG(sc.score),2)
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname
ORDER BY
AVG(sc.score) DESC
LIMIT 1;

8. Total revenue generated by all courses:


SELECT
ROUND(SUM(c.cost),2) AS total_revenue
FROM
courses c;

9 Calculate the cost of all courses taken by a specific student.

SELECT
s.lastname, s.firstname,
ROUND(SUM(c.cost),2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
s.studentid = 6
GROUP BY s.lastname, s.firstname;

10. Total amount spent by a specific student (e.g., student with ID)
SELECT
ROUND(SUM(c.cost),2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
s.studentid = 6
AND sc.paid = 1;

11. Total revenue generated by a specific course (e.g., 'C++')


SELECT
c.coursename,
ROUND(SUM(c.cost),2) AS course_revenue
FROM
courses c
WHERE
c.coursename = 'C++'
GROUP BY c.coursename;

12. Total amount spent by all students on a specific course (e.g., 'Visual
Basic')
SELECT
c.coursename,

ROUND(SUM(c.cost),2) AS total_student_spending

FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = 'Visual Basic'
GROUP BY c.coursename;

13. Average amount spent per student across all courses.

SELECT
s.lastname,
s.firstname,
ROUND(AVG(c.cost),2) AS avg_student_spending
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
GROUP BY
s.lastname,
s.firstname
ORDER BY s.lastname,
s.firstname;

14. Total commission for all teachers (Assuming a 10% commission for teachers
)
SELECT
ROUND(SUM(c.cost * 0.1),2) AS total_teacher_earnings
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid;
-- Assuming a 10% commission for teachers

15. Find the course with the highest total revenue


SELECT
c.coursename,
ROUND(SUM(c.cost),2) AS total_revenue
FROM
courses c
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
c.coursename
ORDER BY
total_revenue DESC
LIMIT 1;

16. Find the student who has spent the most on courses.
SELECT
s.studentid,
s.firstname,
s.lastname,
ROUND(SUM(c.cost),2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE sc.paid=1
GROUP BY s.firstname,
s.lastname, s.studentid
ORDER BY total_spent DESC
LIMIT 1;

17. Calculate the average cost of all courses.


SELECT
ROUND(AVG(cost),2) AS avg_course_cost
FROM
courses;

18. Find the most expensive course(s).


SELECT
c.coursename,
c.cost
FROM
courses c
WHERE
c.cost = (SELECT MAX(cost) FROM courses);

19. Find the least expensive course.

SELECT
c.coursename,
c.cost
FROM
courses c
WHERE
c.cost = (SELECT MIN(cost) FROM courses);

20. Find the total revenue generated by each teacher (Assuming a 10%
commission for teachers).
SELECT
t.firstname,
t.lastname,
ROUND(SUM(c.cost * 0.1), 2) AS teacher_revenue
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname;
-- Assuming a 10% commission for teachers

21. Find the average student spending per course


SELECT
c.coursename,
ROUND( (SUM(c.cost) / COUNT(DISTINCT sc.studentid)),2) AS
avg_student_spending_per_course
FROM
courses c
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
c.coursename;

22. Find the average student spending per course


SELECT
c.coursename,
ROUND( (SUM(c.cost) / COUNT(DISTINCT sc.studentid)), 2) AS
avg_student_spending_per_course;
FROM
courses c
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY c.coursename;

Stored Procedures from the homework

1. Create a Stored procedure that finds the number of students for each teacher.
DELIMITER $$

CREATE PROCEDURE GetStudentNumber()


BEGIN

SELECT
t.firstname,
t.lastname,
COUNT(DISTINCT sc.studentid) as num_of_students
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname
ORDER BY
COUNT(DISTINCT sc.studentid) DESC;
END $$

DELIMITER ;

Call GetStudentNumber();

firstname lastname num_of_students


Julia Williams 8
Michael Ross 7
John Jefferson 6
Michael Murphy 3
Greg Gerald 3
John Smith 2
James Barry 1
John Niven 1
Michael Kelly 1

2. Create a Stored procedure that lists all courses taught by a specific teacher.
DELIMITER $$

CREATE PROCEDURE getTeacherCourses(IN p_lname VARCHAR(50))


BEGIN

SELECT
c.coursename,
t.lastname,
t.firstname
FROM
courses c
JOIN
schedule sch ON c.courseid = sch.courseid
JOIN
teachers t ON sch.teacherid = t.teacherid
WHERE
t.lastname = p_lname;
END $$

DELIMITER ;

Call getTeacherCourses('Jefferson');
coursename lastname firstname
Java Jefferson John
C++ Jefferson John
Java Jefferson John
Java Jefferson John
3. Create a Stored procedure that finds the teacher who teaches the most
students
DELIMITER $$

CREATE PROCEDURE TeacherWithMostStudents()


BEGIN

SELECT
t.firstname,
t.lastname,
COUNT(DISTINCT sc.studentid) as num_of_students
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.teacherid,
t.firstname,
t.lastname
ORDER BY
COUNT(DISTINCT sc.studentid) DESC
LIMIT 1;

END $$

DELIMITER ;

Call TeacherWithMostStudents();
firstname lastname num_of_students
Julia Williams 8
4. Create a Stored procedure that lists all students who have taken a specific
course.
DELIMITER $$

CREATE PROCEDURE getStudentsForCourses(IN p_course VARCHAR(50))


BEGIN

SELECT
s.firstname,
s.lastname,
c.coursename
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = p_course;

END $$

DELIMITER ;

call getStudentsForCourses('C++');

firstname lastname coursename


Michael Johnson C++
Molly James C++
Ann Williams C++
5. Create a Stored procedure that finds the teacher with the highest average
student score across all their courses
DELIMITER $$

CREATE PROCEDURE getTeacherWithHighestStudentScore()


BEGIN

SELECT
t.firstname,
t.lastname,
ROUND(AVG(sc.score), 2) AS highest_score
FROM
teachers t
JOIN
schedule sch ON t.teacherid = sch.teacherid
JOIN
courses c ON sch.courseid = c.courseid
JOIN
student_course sc ON c.courseid = sc.courseid
GROUP BY
t.firstname,
t.lastname
ORDER BY
AVG(sc.score) DESC
LIMIT 1;

END $$

DELIMITER;

Call getTeacherWithHighestStudentScore ();

firstname lastname highest_score


John Jefferson 83.75
6. Create a Stored procedure that calculates the cost of all courses a specific
student takes.
The input parameter is a student's lastname.
DELIMITER $$

CREATE PROCEDURE getCoursesCostPerStudent(IN p_lastname VARCHAR(50))


BEGIN

SELECT
s.firstname,
s.lastname,
ROUND(SUM(c.cost), 2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
s.lastname=p_lastname
GROUP BY s.firstname, s.lastname;

END $$

DELIMITER;
Call getCoursesCostPerStudent('Brown');

firstname lastname total_spent


Ryan Brown 4699.85
Cindy Brown 10399.75
7. Create a Stored procedure that calculates the total amount spent by a specific
student. The input parameter is a student's lastname.
DELIMITER $$

CREATE PROCEDURE getStudentAmountSpent(IN p_lastname VARCHAR(50))


BEGIN

SELECT
s.firstname,
s.lastname,
ROUND(SUM(c.cost),2) AS total_spent
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
s.lastname = p_lastname
AND sc.paid = 1
GROUP BY s.firstname, s.lastname;

END $$

DELIMITER;

Call getStudentAmountSpent('Brown');

firstname lastname total_spent


Ryan Brown 2999.9
Cindy Brown 8699.8
8. Create a Stored procedure that calculates the total amount spent by all
students on a specific course.
DELIMITER $$

CREATE PROCEDURE getAmountSpentPerCourse(IN p_course VARCHAR(50))


BEGIN
SELECT
c.coursename,
ROUND(SUM(c.cost),2) AS total_student_spending
FROM
students s
JOIN
student_course sc ON s.studentid = sc.studentid
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = p_course
GROUP BY c.coursename;

END $$

DELIMITER ;

Call getAmountSpentPerCourse('Java');
coursename total_student_spending
Java 14999.75
9. Create a Stored procedure that finds the least expensive course.
DELIMITER $$

CREATE PROCEDURE getLeastExpensiveCourse()


BEGIN
SELECT
coursename,
cost
FROM
courses
WHERE
cost = (SELECT MIN(cost) FROM courses);

END $$

DELIMITER ;

Call getLeastExpensiveCourse();
coursename cost
HTML 699.95
HTML 699.95
10. Create a Stored procedure that finds the average cost of all courses
DELIMITER $$
CREATE PROCEDURE getAvgCoursesCost()
BEGIN

SELECT
ROUND(AVG(cost),2) AS avg_course_cost
FROM
courses;

END $$

DELIMITER;

Call getAvgCoursesCost();
avg_course_cost
1733.29

Functions from the homework


1. Create a function that returns the number of characters in a string.
DELIMITER $$

CREATE FUNCTION get_string_length(inputString VARCHAR(255))


RETURNS INT
DETERMINISTIC
BEGIN
RETURN CHAR_LENGTH(inputString);
END$$

DELIMITER ;

SELECT lastname, get_string_length(lastname) as name_length FROM


Teachers order by name_length;

lastname name_length
Ross 4
Smith 5
Barry 5
Niven 5
Merry 5
Kelly 5
Karon 5
Murphy 6
Gerald 6
Williams 8
Jefferson 9
Creating a get_string_length() function is unnecessary. The LENGTH() SQL
function returns the length of a string.
SELECT lastname, LENGTH(lastname) as name_length FROM Teachers order by
name_length;

lastname name_length
Ross 4
Smith 5
Barry 5
Niven 5
Merry 5
Kelly 5
Karon 5
Murphy 6
Gerald 6
Williams 8
Jefferson 9
2. Create a function that calculates the average cost of all courses.
DELIMITER //

CREATE FUNCTION get_avg_courses_cost()


RETURNS DECIMAL(10, 2) -- Adjust precision and scale as needed
DETERMINISTIC -- Or READS SQL DATA if the function reads other data
BEGIN
DECLARE v_avg_cost DECIMAL(10, 2);

SELECT ROUND(AVG(cost), 2) INTO v_avg_cost


FROM courses;

RETURN v_avg_cost;
END //

DELIMITER ;

SELECT get_avg_courses_cost();
get_avg_courses_cost()

1733.29
SELECT get_avg_courses_cost() as Avg_Courses_Cost;
Avg_Courses_Cost
1733.29
3. Create a function that calculates the total amount spent by a specific student.
(The input parameter is a student's lastname.)
DELIMITER //

CREATE FUNCTION get_student_amount_spent(p_lastname VARCHAR(50))


RETURNS DECIMAL(10, 2)
DETERMINISTIC -- Or READS SQL DATA if the function reads other data
BEGIN
DECLARE v_total_spent DECIMAL(10, 2);

SELECT ROUND(SUM(c.cost), 2) INTO v_total_spent


FROM students s
JOIN student_course sc ON s.studentid = sc.studentid
JOIN courses c ON sc.courseid = c.courseid
WHERE s.lastname = p_lastname
AND sc.paid = 1;

-- Handle the case where the student is not found or has not spent anything
IF v_total_spent IS NULL THEN
SET v_total_spent = 0.00; -- Or you could return NULL if you prefer
END IF;

RETURN v_total_spent;
END //

DELIMITER ;

SELECT get_student_amount_spent('Brown');
get_student_amount_spent('Brown')
11699.70
SELECT get_student_amount_spent('Brown') as
student_amount_spent;
student_amount_spent
11699.70
4. Create a function that calculates the total cost of all courses.
DELIMITER //

CREATE FUNCTION get_total_courses_cost()


RETURNS DECIMAL(10, 2)
READS SQL DATA -- Important: READS SQL DATA because it reads from the
courses table
BEGIN
DECLARE v_total_cost DECIMAL(10, 2);

SELECT SUM(cost) INTO v_total_cost


FROM courses;

-- Handle the case where there are no courses (optional)


IF v_total_cost IS NULL THEN
SET v_total_cost = 0.00; -- Or NULL if you prefer
END IF;

RETURN v_total_cost;
END //

DELIMITER ;

SELECT get_total_courses_cost();
get_total_courses_cost()
41598.85
SELECT get_total_courses_cost() as total_courses_cost;
total_courses_cost
41598.85
5. Create a function that returns the number of students in a specific course.
DELIMITER //

CREATE FUNCTION get_number_of_students_in_course(p_coursename


VARCHAR(255))
RETURNS INT
READS SQL DATA
BEGIN
DECLARE v_student_count INT;

SELECT
COUNT(DISTINCT sc.studentid) INTO v_student_count
FROM
student_course sc
JOIN
courses c ON sc.courseid = c.courseid
WHERE
c.coursename = p_coursename;

IF v_student_count IS NULL THEN


SET v_student_count = 0;
END IF;

RETURN v_student_count;
END //

DELIMITER ;

SELECT get_number_of_students_in_course('C++');

get_number_of_students_in_course('C++')
3
SELECT get_number_of_students_in_course('C++')
as student_number;
student_number
3
6. Create a function that returns the average student score across all courses.
DELIMITER //

CREATE FUNCTION get_average_student_score_all_courses()


RETURNS DECIMAL(5,2)
READS SQL DATA
BEGIN
DECLARE v_avg_score DECIMAL(5,2);

SELECT AVG(score) INTO v_avg_score


FROM student_course;

IF v_avg_score IS NULL THEN


SET v_avg_score = 0.00;
END IF;

RETURN v_avg_score;
END //

DELIMITER ;

SELECT get_average_student_score_all_courses() as Avg_Score;


Avg_Score
75.29
7. Create a function that finds the cost of a specific course
DELIMITER //

CREATE FUNCTION get_course_cost(p_coursename VARCHAR(255))


RETURNS DECIMAL(10, 2)
READS SQL DATA
BEGIN
DECLARE v_course_cost DECIMAL(10, 2);
SELECT cost INTO v_course_cost
FROM courses
WHERE coursename = p_coursename
ORDER by cost
LIMIT 1;

IF v_course_cost IS NULL THEN


SET v_course_cost = 0.00; -- Or NULL
END IF;

RETURN v_course_cost;
END //

DELIMITER;

SELECT get_course_cost('C++') as course_cost;


course_cost
2999.95
8. Create a function that finds the teacher with the highest average student score
across all their courses
DELIMITER //

CREATE FUNCTION get_teacher_with_highest_student_score()


RETURNS VARCHAR(255) -- Returns the teacher's full name
READS SQL DATA
BEGIN
DECLARE v_teacher_name VARCHAR(255);
SELECT CONCAT(t.firstname, ' ', t.lastname) INTO v_teacher_name
FROM teachers t
JOIN schedule sch ON t.teacherid = sch.teacherid
JOIN courses c ON sch.courseid = c.courseid
JOIN student_course sc ON c.courseid = sc.courseid
GROUP BY t.teacherid, t.firstname, t.lastname -- Group by teacher ID as well
for uniqueness
ORDER BY AVG(sc.score) DESC
LIMIT 1;

RETURN v_teacher_name;
END //

DELIMITER ;

SELECT get_teacher_with_highest_student_score();
get_teacher_with_highest_student_score()
John Jefferson
9. Create a function that finds the teacher with the lowest average student score
across all their courses
DELIMITER //

CREATE FUNCTION get_teacher_with_lowest_student_score()


RETURNS VARCHAR(255)
READS SQL DATA
BEGIN
DECLARE v_teacher_name VARCHAR(255);

SELECT CONCAT(t.firstname, ' ', t.lastname) INTO v_teacher_name


FROM teachers t
JOIN schedule sch ON t.teacherid = sch.teacherid
JOIN courses c ON sch.courseid = c.courseid
JOIN student_course sc ON c.courseid = sc.courseid
GROUP BY t.teacherid, t.firstname, t.lastname
ORDER BY AVG(sc.score) ASC
LIMIT 1;

RETURN v_teacher_name; -- Semicolon here


END // -- END for the BEGIN block

DELIMITER ;

SELECT get_teacher_with_lowest_student_score();
get_teacher_with_lowest_student_score()
Michael Kelly

10 Create a function that calculates the average score difference between


morning and afternoon sections of the same course.
DELIMITER //

CREATE FUNCTION get_average_score_difference(p_coursename VARCHAR(255))


RETURNS DECIMAL(5, 2)
READS SQL DATA
BEGIN
DECLARE v_avg_morning DECIMAL(5, 2);
DECLARE v_avg_afternoon DECIMAL(5, 2);
DECLARE v_difference DECIMAL(5, 2);

-- Get average score for the morning session


SELECT AVG(sc.score) INTO v_avg_morning
FROM student_course sc
JOIN courses c ON sc.courseid = c.courseid
JOIN schedule s ON c.courseid = s.courseid
WHERE c.coursename = p_coursename AND TIME(s.starttime) <
TIME('12:00:00');

-- Get average score for the afternoon session


SELECT AVG(sc.score) INTO v_avg_afternoon
FROM student_course sc
JOIN courses c ON sc.courseid = c.courseid
JOIN schedule s ON c.courseid = s.courseid
WHERE c.coursename = p_coursename AND TIME(s.starttime) >=
TIME('12:00:00');

-- Calculate the difference (handle NULLs)


IF v_avg_morning IS NULL THEN
SET v_avg_morning = 0; -- Or NULL if you prefer
END IF;

IF v_avg_afternoon IS NULL THEN


SET v_avg_afternoon = 0; -- Or NULL
END IF;

SET v_difference = ABS(v_avg_morning - v_avg_afternoon); -- Absolute


difference

RETURN v_difference;
END //

DELIMITER ;
SELECT get_average_score_difference('C++') as moning_afternoon_diff;
moning_afternoon_diff
36.50
SELECT get_average_score_difference('Java')
as moning_afternoon_diff;

moning_afternoon_diff
9.00
Let’s remove the ABS function that displays a negative value as a positive.
DELIMITER //

CREATE FUNCTION get_average_score_diff(p_coursename


VARCHAR(255))
RETURNS DECIMAL(5, 2)
READS SQL DATA
BEGIN
DECLARE v_avg_morning DECIMAL(5, 2);
DECLARE v_avg_afternoon DECIMAL(5, 2);
DECLARE v_difference DECIMAL(5, 2);

-- Get average score for the morning session


SELECT AVG(sc.score) INTO v_avg_morning
FROM student_course sc
JOIN courses c ON sc.courseid = c.courseid
JOIN schedule s ON c.courseid = s.courseid
WHERE c.coursename = p_coursename AND TIME(s.starttime) <
TIME('12:00:00');

-- Get average score for the afternoon session


SELECT AVG(sc.score) INTO v_avg_afternoon
FROM student_course sc
JOIN courses c ON sc.courseid = c.courseid
JOIN schedule s ON c.courseid = s.courseid
WHERE c.coursename = p_coursename AND TIME(s.starttime) >=
TIME('12:00:00');

-- Calculate the difference (handle NULLs)


IF v_avg_morning IS NULL THEN
SET v_avg_morning = 0; -- Or NULL if you prefer
END IF;
IF v_avg_afternoon IS NULL THEN
SET v_avg_afternoon = 0; -- Or NULL
END IF;

SET v_difference = v_avg_morning - v_avg_afternoon; -- Absolute difference

RETURN v_difference;
END //

DELIMITER ;

SELECT get_average_score_diff('C++')
as moning_afternoon_diff;
moning_afternoon_diff
-36.50
SELECT get_average_score_diff('Java')
as moning_afternoon_diff;
moning_afternoon_diff

9.00

Resources
You can practice your SQL skills on this webpage. It’s linked to a
MySQL database that includes all the necessary tables:
https://www.learn-coding.today/continue_learn_sql.php
You can download a text file containing the CREATE TABLE statements and
data for each table used in this book from the following link:
https://www.learn-coding.today/continue_sql/tables.txt

Thank you for reading! If you found this book helpful, I would
greatly appreciate it if you could leave a brief review on Amazon.
Your support truly makes a difference and helps me improve this
book further.
Would you be willing to share how this book has benefited you?
Your feedback will assist other readers in gaining from this work.
If you'd rather not, that's perfectly fine. Thank you once again for
your support!

Author's Books on Amazon.com

Learn SQL By Examples: Examples of SQL Queries and Stored


Procedures for MySQL and Oracle Databases.
C++ Programming by Examples: Key Computer Programming
Concepts for Beginners.

PHP Programming for Beginners: Key Programming Concepts. How


to use PHP with MySQL and Oracle database
The Ultimate eBook Creator: A Master Guide on How to Create,
Design, and Format Your eBook Using Free Software

How to Create and Store Your Passwords: Hacker's Guide


The Easiest Way to Understand Algebra: Algebra equations with
answers and solutions

Geometry For Students and Parents: Key concepts, problems, and


solutions
ABOUT THE AUTHOR
Sergey Skudaev currently resides in Florida. He earned a master's degree in
biology from a foreign university, where he specialized in neurophysiology. He
also holds a degree in Computer Science from BMCC, which he earned after
immigrating to the US. Since then, Sergey has worked as a software quality
specialist and web developer for a computer company in Florida. He has over
ten years of teaching experience and a longstanding interest in new computer
technologies, psychology, and brain physiology. When he has time to relax,
Sergey enjoys swimming in the Gulf of Mexico or walking with his dog on the
Honeymoon Island.

You might also like