Little-Lemon-Restaurant-Database
This project is the Database Engineer Capstone guided by Meta. It passes through many steps:
- Setting a database
- Creating reports
- The orders placed in the restaurant
- The customers with specific orders
- The best selling menu items
- Query Optimization
- Displays the maximum ordered quantity in the Orders table
- Return information about an order
- Delete a specific order with its id
- Check the booking status of any table in the restaurant
- Verify a booking, and decline any reservations for tables that are already booked under another name
- Update existing bookings in the booking table
- Cancel a booking
- Creating database clients
1. Setting a database
The information required to be stored in this database
-
Bookings: To store information about booked tables in the restaurant including booking id, date and table number.
-
Orders: To store information about each order such as order date, quantity and total cost.
-
Order delivery status: To store information about the delivery status of each order such as delivery date and status.
-
Menu: To store information about cuisines, starters, courses, drinks and desserts.
-
Customer details: To store information about the customer names and contact details.
-
Staff information: Including role and salary.
Main entites and related attributes
-
Customers:
- PK → Customer ID
- Attributes → Full Name - Phone Number
-
Staff:
- PK → Staff ID
- Attributes → Name - Role - Salary - Address - Contact Number - Email
-
Mene Items:
- PK → Item ID
- Attributes → Item name - Category - Cuisine - Price
-
Bookings:
- PK → Booking ID
- Attributes → Booking date - Customer - Table number - Number of Persons - Staff
-
Orders:
- PK → Order ID
- Attributes → Table number - Order date - Total cost - Booking ID - Items and Quantity
-
Orders Delivery Status:
- PK → Order ID
- Attributes → Order ID - Delivery Date - Delivery Status
Normalization Process
- The tables Orders have multi-valued attrbiutes such as Items and Quantities
- The solution is to split this table into 2 tables:
- Orders → The original table without the items and quantity
- Orders_Details
- PK → Order ID & Item ID
- Attributes → Quantity
Final Desgin of the Database
This design was implemented using MySQL Workbench tool.
Implement the databse into MySQL Sever
Using the Forward Engineer feature in MySQL Workbench, we can implement the database into the server using its ER-Diagram.
Here is some snippets of the code generated by the Forward Engineer tool.
-
Creating the whole schema
CREATE SCHEMA IF NOT EXISTS `little_lemon_db` DEFAULT CHARACTER SET utf8 ; USE `little_lemon_db` ;
-
Creating the Bookings table
CREATE TABLE IF NOT EXISTS `little_lemon_db`.`Bookings` ( `Booking_ID` INT NOT NULL, `Booking_Date` DATETIME NOT NULL, `Customer_ID` INT NOT NULL, `Table_number` INT NOT NULL, `Number_of_Persons` INT NOT NULL, `Staff_ID` INT NOT NULL, PRIMARY KEY (`Booking_ID`), INDEX `FK_customers_in_bookings_idx` (`Customer_ID` ASC) VISIBLE, INDEX `FK_staff_in_bookings_idx` (`Staff_ID` ASC) VISIBLE, CONSTRAINT `FK_customers_in_bookings` FOREIGN KEY (`Customer_ID`) REFERENCES `little_lemon_db`.`Customers` (`Customer_ID`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `FK_staff_in_bookings` FOREIGN KEY (`Staff_ID`) REFERENCES `little_lemon_db`.`Staff` (`Staff_ID`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB;
-
Creating the Menu table
CREATE TABLE IF NOT EXISTS `little_lemon_db`.`Menu` ( `Item_ID` INT NOT NULL, `Name` VARCHAR(45) NOT NULL, `Category` VARCHAR(45) NOT NULL, `Cuisine` VARCHAR(45) NOT NULL, `Price` INT NOT NULL, PRIMARY KEY (`Item_ID`)) ENGINE = InnoDB;
The whole code file is at the database-codes folder in the repo.
2. Creating reports
Little Lemon Restaurant needs some reports to monitor:
- The orders placed in the restaurant.
- The customers with specific orders.
- The menu items with specific number of orders.
And some features such as:
- Delete a specific order
- Check bookings
- Add valid bookings
- Update bookings
- Cancel Bookings
And to do those tasks we need to use some clauses and features from SQL like Views, Stored Procedures, Triggers and Prepare Statements.
Task 1 : The orders placed in the restaurant
Little Lemon needs to know some information about the order placed in the restaurant like OrderID, Quantity, and Total Cost. It results in:
mysql> SELECT * FROM OrdersView;
+---------+----------+------------+
| OrderID | Quantity | Total_Cost |
+---------+----------+------------+
| 1 | 7 | 890 |
| 2 | 6 | 800 |
+---------+----------+------------+
2 rows in set (0.00 sec)
Task 2 : All customers with orders that cost more than $150
Little Lemon needs to know some information about the customers with specific orders. It results in:
SELECT C.Customer_ID, C.Full_Name, O.Order_ID, (M.Item_ID * M.Price) AS Cost, M.Name AS MenuName
FROM Customers AS C
INNER JOIN Bookings AS B
ON B.Customer_ID = C.Customer_ID
INNER JOIN Orders AS O
ON O.Booking_ID = B.Booking_ID
INNER JOIN Orders_Details AS OD
ON OD.OrderID = O.Order_ID
INNER JOIN Menu AS M
ON M.Item_ID = OD.Item_ID
WHERE O.Total_Cost > 150;
+-------------+---------------+----------+------+----------+
| Customer_ID | Full_Name | Order_ID | Cost | MenuName |
+-------------+---------------+----------+------+----------+
| 1 | Rabia Mendoza | 1 | 150 | Pasta |
| 1 | Rabia Mendoza | 1 | 140 | Salad |
| 2 | Aayan Chaney | 2 | 150 | Pasta |
| 2 | Aayan Chaney | 2 | 300 | Fried |
+-------------+---------------+----------+------+----------+
4 rows in set (0.06 sec)
Task 3 : All menu items for which more than 2 orders have been placed
Little Lemon needs to know some information about the menu items for which more than 2 orders have been placed. It results in:
SELECT Name AS MenuName FROM Menu
WHERE Name = ANY(SELECT M.Name FROM Menu AS M
INNER JOIN Orders_Details AS OD
ON OD.Item_ID = M.Item_ID);
+----------+
| MenuName |
+----------+
| Pasta |
| Salad |
| Fried |
+----------+
3 rows in set (0.14 sec)
Query Optimization
When working with MySQL databases, the response time, or turnaround time, is extremely important. It’s particularly important in terms of how long the database takes to respond to your SQL queries. As data volumes grow, and the data requirements grow increasingly more complex, then performance becomes more important for a better end-user experience.
Database optimization is the best way to reduce database system response time. Response time is the time taken to transmit a query, process it, and transmit the response or information back to the user.
We can optimize database queries using stored procedures and prepared statements.
Task 1 : Displays the maximum ordered quantity in the Orders table
This was done using a Stored Procedure called GetMaxQuantity(). It results in:
mysql> CALL GetMaxQuantity();
+-----------------------+
| Max Quantity in Order |
+-----------------------+
| 7 |
+-----------------------+
1 row in set (0.08 sec)
Query OK, 0 rows affected (0.08 sec)
Task 2 : Return information about an order
This was done using a Prepared Statement called GetOrderDetail. It results in:
mysql> SET @id = 1;
Query OK, 0 rows affected (0.05 sec)
mysql> EXECUTE GetOrderDetail USING @id;
+---------+----------+------------+
| OrderID | Quantity | Total_Cost |
+---------+----------+------------+
| 1 | 7 | 890 |
+---------+----------+------------+
1 row in set (0.04 sec)
Task 3 : Delete a specific order with its id
This was done using a Stored Procedure called CancelOrder. It takes the Order_ID and delete the order from the Orders, the Orders_Details, and the Orders_Delivery_Status tables.
mysql> CALL CancelOrder(1);
+---------------------+
| Confirmation |
+---------------------+
| Order 1 is canceled |
+---------------------+
1 row in set (0.32 sec)
Query OK, 0 rows affected (0.32 sec)
Task 4 : Check the booking status of any table in the restaurant
This was done using a Stored Procedure called CheckBooking. It takes 2 input:
- Booking date
- Table number And then check if the booking already exists in the Bookings table or not.
mysql> CALL CheckBooking("2022-08-10 12:00:00", 5);
+----------------------------+
| Booking Status |
+----------------------------+
| Table 5 is already booked. |
+----------------------------+
1 row in set (0.05 sec)
Query OK, 0 rows affected (0.05 sec)
Task 5 : Verify a booking, and decline any reservations for tables that are already booked under another name
This was done using a Stored Procedure called AddValidBooking. It takes 4 inputs:
- Booking date
- Table number
- Customer ID
- Number of persons
And check if there's an existing booking at the same time on the same table number, it prints that there's already an existing booking. Else, it stores this booking into the Bookings table.
-
If there's already a booking at this time on the same table number.
mysql> CALL AddValidBooking("2022-08-10 12:00:00", 5, 1, 5); +-----------------------------------------------+ | Booking Status | +-----------------------------------------------+ | Table 5 is already booked. Booking cancelled. | +-----------------------------------------------+ 1 row in set (0.03 sec) Query OK, 0 rows affected (0.03 sec)
-
If the booking date is different from all the bookings.
mysql> CALL AddValidBooking("2022-10-10 12:00:00", 5, 1, 5); +----------------------------+ | Booking Status | +----------------------------+ | Table 5 is booked for you. | +----------------------------+ 1 row in set (0.07 sec) Query OK, 0 rows affected (0.07 sec) mysql> SELECT * FROM Bookings; +------------+---------------------+-------------+--------------+-------------------+----------+ | Booking_ID | Booking_Date | Customer_ID | Table_number | Number_of_Persons | Staff_ID | +------------+---------------------+-------------+--------------+-------------------+----------+ | 1 | 2022-08-10 12:00:00 | 1 | 5 | 10 | 2 | | 2 | 2022-09-15 03:30:00 | 2 | 4 | 4 | 2 | | 3 | 2022-10-10 12:00:00 | 1 | 5 | 5 | 2 | +------------+---------------------+-------------+--------------+-------------------+----------+ 3 rows in set (0.00 sec)
-
If the table number is different from all the bookings.
mysql> CALL AddValidBooking("2022-10-10 12:00:00", 2, 1, 5); +----------------------------+ | Booking Status | +----------------------------+ | Table 2 is booked for you. | +----------------------------+ 1 row in set (0.03 sec) Query OK, 0 rows affected (0.04 sec) mysql> SELECT * FROM Bookings; +------------+---------------------+-------------+--------------+-------------------+----------+ | Booking_ID | Booking_Date | Customer_ID | Table_number | Number_of_Persons | Staff_ID | +------------+---------------------+-------------+--------------+-------------------+----------+ | 1 | 2022-08-10 12:00:00 | 1 | 5 | 10 | 2 | | 2 | 2022-09-15 03:30:00 | 2 | 4 | 4 | 2 | | 3 | 2022-10-10 12:00:00 | 1 | 5 | 5 | 2 | | 4 | 2022-10-10 12:00:00 | 1 | 2 | 5 | 2 | +------------+---------------------+-------------+--------------+-------------------+----------+ 4 rows in set (0.05 sec)
Task 6 : Update existing bookings in the booking table
This was done using a Stored Procedure called UpdateBooking. It takes 2 inputs:
- Booking ID
- The new booking date
-
If the booking already exists
mysql> CALL UpdateBooking(2, "2022-09-20 3:30"); +-----------------------+ | Confirmation | +-----------------------+ | Booking 2 is updated. | +-----------------------+ 1 row in set (0.09 sec) Query OK, 0 rows affected (0.10 sec) mysql> SELECT * FROM Bookings; +------------+---------------------+-------------+--------------+-------------------+----------+ | Booking_ID | Booking_Date | Customer_ID | Table_number | Number_of_Persons | Staff_ID | +------------+---------------------+-------------+--------------+-------------------+----------+ | 1 | 2022-08-10 12:00:00 | 1 | 5 | 10 | 2 | | 2 | 2022-09-20 03:30:00 | 2 | 4 | 4 | 2 | | 3 | 2022-10-10 12:00:00 | 1 | 5 | 5 | 2 | | 4 | 2022-10-10 12:00:00 | 1 | 2 | 5 | 2 | +------------+---------------------+-------------+--------------+-------------------+----------+ 4 rows in set (0.00 sec)
-
If the booking doesn't exist.
mysql> CALL UpdateBooking(7, "2022-09-20 3:30"); +---------------------------------------+ | Confirmation | +---------------------------------------+ | There is no booking with booking id 7 | +---------------------------------------+ 1 row in set (0.03 sec) Query OK, 0 rows affected (0.03 sec)
Task 7 : Cancel a booking
This was done using a Stored Procedure called CancelBooking. It takes the Booking ID which the customer wants to cancel, and delete the booking from the Bookings table, as well as delete the order related to this booking from the Orders, Orders_Details, and Orders_Delivery_Status.
-
If the booking already exists.
mysql> CALL CancelBooking(2); +-------------------------+ | Confirmation | +-------------------------+ | Booking 2 is cancelled. | +-------------------------+ 1 row in set (0.29 sec) Query OK, 0 rows affected (0.29 sec)
-
If the booking doesn't exists.
mysql> CALL CancelBooking(8); +---------------------------------------+ | Confirmation | +---------------------------------------+ | There is no booking with booking id 8 | +---------------------------------------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.01 sec)
3. Creating database clients
Little Lemon Restaurant needs a database client to deal with the database using a Python Appliction. To do this task we need to connect python to the database, and this can be done using mysql-connector-python
.
First, I've imported the library then established the connection.
connection = connector.connect(
user = ENV_DATABASE_USER,
password = ENV_DATABASE_PASSWORD,
host = ENV_DATABASE_HOST,
port = ENV_DATABASE_PORT)
After that, I've created a cursor to link the Pyhon Application to the database, and use the database.
cursor = connection.cursor()
use_database = """USE Little_Lemon_DB;"""
cursor.execute(use_database)
Finally, Little Lemon now has a database client, and can monitor the information they need through a Python Application.
I've done some tasks using this application:
-
Show the tables of the database
show_tables_query = """SHOW TABLES;""" cursor.execute(show_tables_query) results = cursor.fetchall() for i, result in enumerate(results): print("Table no. ", i+1, ": ", result[0])
and this gave me the following results:
Table no. 1 : bookings Table no. 2 : customers Table no. 3 : menu Table no. 4 : orders Table no. 5 : orders_delivery_status Table no. 6 : orders_details Table no. 7 : ordersview Table no. 8 : staff
-
Extract some information about the customers who paid more than 60$ for the purpose of promotional campaign
promotional_campaign = """ SELECT C.Full_Name, C.Phone_Number, O.Total_Cost FROM Customers AS C INNER JOIN Bookings AS B ON B.Customer_ID = C.Customer_ID INNER JOIN Orders AS O ON O.Booking_ID = B.Booking_ID WHERE O.Total_Cost > 60; """ cursor.execute(promotional_campaign) results = cursor.fetchall() print("Information about customers for the purpose of promotional campaign:") for i, result in enumerate(results): print("Customer no. ", i+1, ": ", result[0], "whose contact is (", result[1], ") has paid ", result[2], "$.")
and this gave me the following results:
Information about customers who win promotional_campaign: Customer no. 1 : Rabia Mendoza whose contact is ( 0123456789 ) has paid 890 $. Customer no. 2 : Aayan Chaney whose contact is ( 0129876543 ) has paid 800 $.