This project analyzes 48,620 rows of real pizza sales data using MySQL for data analysis and Power BI for interactive dashboard visualization.
The goal was to extract meaningful business insights from raw sales data β including revenue trends, peak ordering hours, best-selling pizzas, and category performance.
π‘ This project was built as part of my Data Analyst internship portfolio. It includes real errors I faced and how I solved them β showing my problem solving ability.
| Tool | Purpose |
|---|---|
| MySQL | Database creation, data cleaning, SQL queries |
| MySQL Workbench | Query execution and database management |
| Power BI Desktop | Interactive dashboard and visualization |
| CSV Dataset | Raw pizza sales data (48,620 rows) |
pizza-sales-analysis/
β
βββ π README.md β Project documentation
β
βββ π sql/
β βββ setup.sql β Clean database setup + data cleaning
β βββ setup_raw.sql β Original setup with errors & fixes
β βββ pizza_sales_queries.sql β All 15 analysis queries
β
βββ π dashboard/
β βββ dashboard.png β Dashboard screenshot
β βββ pizza_sales.pdf β Dashboard PDF export
β
βββ π query_screenshots/ β Query Outputs
β βββ Query_1.png
β βββ Query_2.png
β βββ ...Query_15.png
β
βββ π pizza_sales.csv β Raw dataset (48,620 rows)
βββ π pizza_sales.pbix β Power BI dashboard file
File: pizza_sales.csv
Rows: 48,620
Columns: 12
| Column | Type | Description |
|---|---|---|
| pizza_id | INT | Unique row identifier |
| order_id | INT | Unique order number |
| pizza_name_id | VARCHAR | Short pizza code |
| quantity | INT | Quantity ordered |
| order_date | DATE | Date of order |
| order_time | TIME | Time of order |
| unit_price | DECIMAL | Price per pizza |
| total_price | DECIMAL | quantity Γ unit_price |
| pizza_size | VARCHAR | S, M, L, XL |
| pizza_category | VARCHAR | Classic, Supreme, Veggie, Chicken |
| pizza_ingredients | TEXT | List of ingredients |
| pizza_name | VARCHAR | Full pizza name |
The dataset had mixed date formats (M/D/YYYY and DD-MM-YYYY) which required cleaning before analysis:
-- Step 1: Change column to TEXT for import
ALTER TABLE pizza_sales MODIFY COLUMN order_date TEXT;
-- Step 2: Fix M/D/YYYY format
SET SQL_SAFE_UPDATES = 0;
UPDATE pizza_sales
SET order_date = STR_TO_DATE(order_date, '%m/%d/%Y')
WHERE order_date LIKE '%/%';
-- Step 3: Fix DD-MM-YYYY format
UPDATE pizza_sales
SET order_date = STR_TO_DATE(order_date, '%d-%m-%Y')
WHERE order_date NOT LIKE '2015%'
AND order_date NOT LIKE '2016%'
AND order_date NOT LIKE '2017%';
SET SQL_SAFE_UPDATES = 1;
-- Step 4: Convert back to DATE type
ALTER TABLE pizza_sales MODIFY COLUMN order_date DATE;π See
sql/setup_raw.sqlfor the complete journey including all errors faced and how they were debugged.
Error: "Incorrect date value: '13-01-2015' for column order_date"
Cause: Dataset had 2 mixed date formats β some rows used M/D/YYYY and others used DD-MM-YYYY.
Solution:
- Changed column type to TEXT before import
- Used
STR_TO_DATE()withWHEREclause to fix each format separately - Converted back to DATE after cleaning
Lesson learned: Always check date formats in dataset before importing!
Error 1175: You are using safe update mode and you tried
to update a table without a WHERE that uses a KEY column
Cause: MySQL's safe mode blocks UPDATE statements without a WHERE clause on a key column.
Solution:
SET SQL_SAFE_UPDATES = 0;
-- run update query
SET SQL_SAFE_UPDATES = 1;Lesson learned: Always turn safe mode back ON after running the update!
Expected: 48,620 rows
Got: 30,096 rows
Cause: Import stopped midway due to date format error in the middle of the file.
Solution:
- Truncated the table to remove partial data
- Fixed
order_datecolumn to TEXT type - Reimported CSV successfully β all 48,620 rows loaded
Lesson learned: Always verify row count after import before running any queries!
Q1. Total orders placed
SELECT COUNT(DISTINCT order_id) AS total_orders
FROM pizza_sales;Result: 21,350 orders
Q2. Total revenue generated
SELECT ROUND(SUM(total_price), 2) AS total_revenue
FROM pizza_sales;Result: $817,860.05
Q3. Highest priced pizza
SELECT DISTINCT pizza_name, pizza_size, unit_price
FROM pizza_sales
ORDER BY unit_price DESC
LIMIT 1;Result: The Greek Pizza (XXL) β $35.95
Q4. Most common pizza size ordered
SELECT pizza_size, COUNT(*) AS order_count
FROM pizza_sales
GROUP BY pizza_size
ORDER BY order_count DESC
LIMIT 1;Result: Large (L)
Q5. Top 5 most ordered pizzas
SELECT pizza_name, SUM(quantity) AS total_qty
FROM pizza_sales
GROUP BY pizza_name
ORDER BY total_qty DESC
LIMIT 5;Q6. Total quantity by pizza category
SELECT pizza_category, SUM(quantity) AS total_quantity
FROM pizza_sales
GROUP BY pizza_category
ORDER BY total_quantity DESC;Q7. Orders by hour of day
SELECT HOUR(order_time) AS order_hour,
COUNT(DISTINCT order_id) AS num_orders
FROM pizza_sales
GROUP BY order_hour
ORDER BY order_hour;Q8. Category wise pizza distribution
SELECT pizza_category,
COUNT(DISTINCT pizza_name) AS num_pizza_types
FROM pizza_sales
GROUP BY pizza_category
ORDER BY num_pizza_types DESC;Q9. Average pizzas ordered per day
SELECT ROUND(AVG(daily_qty), 2) AS avg_pizzas_per_day
FROM (
SELECT order_date, SUM(quantity) AS daily_qty
FROM pizza_sales
GROUP BY order_date
) AS daily_totals;Q10. Top 3 revenue generating pizzas
SELECT pizza_name,
ROUND(SUM(total_price), 2) AS revenue
FROM pizza_sales
GROUP BY pizza_name
ORDER BY revenue DESC
LIMIT 3;Q11. % revenue contribution by pizza category
SELECT pizza_category,
ROUND(SUM(total_price), 2) AS category_revenue,
ROUND(SUM(total_price) * 100.0 /
SUM(SUM(total_price)) OVER (), 2) AS revenue_pct
FROM pizza_sales
GROUP BY pizza_category
ORDER BY revenue_pct DESC;Q12. Cumulative revenue over time
SELECT order_date,
ROUND(SUM(total_price), 2) AS daily_revenue,
ROUND(SUM(SUM(total_price)) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
), 2) AS cumulative_revenue
FROM pizza_sales
GROUP BY order_date
ORDER BY order_date;Q13. Top 3 pizzas by revenue per category
WITH pizza_revenue AS (
SELECT pizza_category, pizza_name,
ROUND(SUM(total_price), 2) AS revenue,
RANK() OVER (
PARTITION BY pizza_category
ORDER BY SUM(total_price) DESC
) AS rnk
FROM pizza_sales
GROUP BY pizza_category, pizza_name
)
SELECT pizza_category, pizza_name, revenue, rnk
FROM pizza_revenue
WHERE rnk <= 3
ORDER BY pizza_category, rnk;Q14. Peak ordering hours
SELECT HOUR(order_time) AS order_hour,
COUNT(DISTINCT order_id) AS num_orders,
RANK() OVER (
ORDER BY COUNT(DISTINCT order_id) DESC
) AS peak_rank
FROM pizza_sales
GROUP BY order_hour
ORDER BY num_orders DESC;Q15. Best selling day of the week
SELECT DAYNAME(order_date) AS day_of_week,
COUNT(DISTINCT order_id) AS num_orders,
ROUND(SUM(total_price), 2) AS revenue
FROM pizza_sales
GROUP BY day_of_week
ORDER BY num_orders DESC;The interactive dashboard includes:
| Visual | Insight |
|---|---|
| 4 KPI Cards | Total Orders, Revenue, Pizzas Sold, Avg Order Value |
| Revenue by Category | Classic leads with $220,053 |
| Orders by Hour | Peak at 12 PM with 2,520 orders |
| Monthly Revenue Trend | Consistent ~$60K-70K per month |
| Top 5 Pizzas | Thai Chicken leads with $43,434 |
| Pizza Size Distribution | Large (45.95%) most popular |
| Best Selling Day | Friday with 3,359 orders |
| Quarter Slicer | Filter dashboard by Q1/Q2/Q3/Q4 |
| Category Slicer | Filter dashboard by pizza category |
- Classic category generates the highest revenue ($220,053 β 26.9% of total)
- Friday is the best selling day with 3,359 orders
- Peak hours are 12 PM and 1 PM β lunch rush drives most sales
- Large size is most popular β 45.95% of all orders
- Thai Chicken Pizza is the top revenue generator at $43,434
- Revenue is consistent throughout the year β no major seasonal dips
- Sunday is the slowest day β opportunity for promotional campaigns
- Install MySQL and MySQL Workbench
- Open
sql/setup.sqlin MySQL Workbench - Run each query step by step using Ctrl + Enter
- Import
pizza_sales.csvusing Table Data Import Wizard - Open
sql/pizza_sales_queries.sql - Run all 15 queries one by one
- Install Power BI Desktop (free from microsoft.com)
- Install MySQL Connector/NET (free from mysql.com)
- Open
pizza_sales.pbix - Refresh data connection if needed
| Concept | Used In |
|---|---|
| COUNT DISTINCT | Q1, Q7, Q14 |
| SUM, ROUND | Q2, Q6, Q10 |
| ORDER BY + LIMIT | Q3, Q4, Q5 |
| GROUP BY + Aggregations | Q1-Q10 |
| HOUR(), DAYNAME() | Q7, Q15 |
| Subqueries | Q9 |
| Window Functions SUM OVER | Q11, Q12 |
| CTEs WITH clause | Q13 |
| RANK() + PARTITION BY | Q13, Q14 |
| STR_TO_DATE() | Data cleaning |
Project: Pizza Sales Analysis Tools: MySQL Β· Power BI Β· SQL Dataset: 48,620 rows of pizza sales data (JanβDec 2015) Purpose: Data Analyst Internship Portfolio Project
