Hands-on SQL JOIN examples with real-world data. Covers INNER, LEFT, RIGHT, FULL OUTER, CROSS, and SELF joins with visual diagrams and interview-ready queries.
- Overview
- Database Schema
- Join Types Demonstrated
- Advanced Queries
- Getting Started
- Sample Results
- Key Learning Outcomes
This project demonstrates practical SQL join operations through a company management database that tracks:
- Employees and their department assignments
- Departments across different locations
- Projects with budgets and timelines
- Employee-Project assignments with roles and hours
The database is designed to showcase real-world scenarios including:
- Employees without department assignments
- Departments with no employees
- Projects without assigned staff
- Many-to-many relationships
employees
├── employee_id (PK)
├── first_name
├── last_name
├── department_id (FK)
├── salary
└── hire_date
departments
├── department_id (PK)
├── department_name
└── location
projects
├── project_id (PK)
├── project_name
├── budget
└── start_date
employee_projects (Junction Table)
├── employee_id (FK)
├── project_id (FK)
├── role
└── hours_worked
employees ──┬─< employee_projects >─┬── projects
│ │
└──> departments
Purpose: Returns only records that have matching values in both tables
Use Case: Get employees with their department information (excludes employees without departments)
Visual Concept:
Table A Table B Result
● ● ●
● ───────── ● ──────► ●
● ○
○
Example Query:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;Expected Output: 7 rows (excludes Lisa Garcia who has no department)
Purpose: Returns all records from the left table + matching records from the right table
Use Case: Get all employees and their departments (includes employees without departments)
Visual Concept:
Table A Table B Result
● ● ●
● ───────── ● ──────► ●
● ○ ● (NULL)
○ ○ (NULL)
Example Query:
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id;Expected Output: 8 rows (includes Lisa Garcia with NULL department)
Purpose: Returns all records from the right table + matching records from the left table
Use Case: Get all departments and their employees (includes empty departments)
Visual Concept:
Table A Table B Result
● ● ●
● ───────── ● ──────► ●
○ ● ● (NULL)
○ ○ (NULL)
Example Query:
SELECT
d.department_name,
e.first_name,
e.last_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;Expected Output: 8 rows (includes Finance department with no employees)
Purpose: Returns all records when there's a match in either left or right table
Use Case: Get complete view of employees and departments (shows all unmatched records from both sides)
Visual Concept:
Table A Table B Result
● ● ●
● ───────── ● ──────► ●
● ○ ● (NULL)
○ ○ (NULL)
● ● (NULL)
○ ○ (NULL)
MySQL Implementation:
-- MySQL uses UNION of LEFT and RIGHT joins
SELECT e.first_name, d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
UNION
SELECT e.first_name, d.department_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;Purpose: Returns Cartesian product (every row from Table A paired with every row from Table B)
Use Case: Generate all possible employee-project combinations for planning
Visual Concept:
Table A (3 rows) × Table B (2 rows) = Result (6 rows)
A1 B1 A1-B1
A2 B2 A1-B2
A3 A2-B1
A2-B2
A3-B1
A3-B2
Example Query:
SELECT
e.first_name,
p.project_name
FROM employees e
CROSS JOIN projects p;Expected Output: 40 rows (8 employees × 5 projects)
Purpose: Join a table to itself to find relationships within the same table
Use Case: Find employee pairs working in the same department
Visual Concept:
employees (as e1) ──┐
├──> Compare department_id
employees (as e2) ──┘
Example Query:
SELECT
e1.first_name AS employee1,
e2.first_name AS employee2,
d.department_name
FROM employees e1
INNER JOIN employees e2
ON e1.department_id = e2.department_id
AND e1.employee_id < e2.employee_id
INNER JOIN departments d
ON e1.department_id = d.department_id;Expected Output: Shows employee pairs from same departments
- Employee count per department
- Average salary by department
- Uses LEFT JOIN to include empty departments
- Identifies employees on multiple projects
- Total hours worked
- Uses HAVING clause with joins
- Team size per project
- Total hours worked
- Cost per hour calculation
- Handles division by zero
- Finds employees not on any project
- Uses LEFT JOIN with WHERE IS NULL pattern
- Cross-departmental project analysis
- Multiple INNER JOINs across 4 tables
- MySQL 5.7+ or MariaDB 10.2+
- MySQL Workbench (recommended) or command-line client
-
Clone or download this repository
git clone https://github.com/yourusername/sql-joins-portfolio.git cd sql-joins-portfolio -
Create the database and tables
mysql -u your_username -p < schema.sql -
Run the join examples
mysql -u your_username -p company_db < sql_joins_examples.sql -
Or use MySQL Workbench:
- Open
schema.sqland execute - Open
sql_joins_examples.sqland run queries individually
- Open
USE company_db;
SELECT COUNT(*) FROM employees; -- Should return 8
SELECT COUNT(*) FROM departments; -- Should return 5
SELECT COUNT(*) FROM projects; -- Should return 5employee_id | first_name | last_name | department_name | location
------------|------------|-----------|-----------------|-------------
101 | John | Smith | Engineering | New York
102 | Sarah | Johnson | Engineering | New York
103 | Michael | Williams | Marketing | Los Angeles
104 | Emily | Brown | Marketing | Los Angeles
105 | David | Jones | Sales | Chicago
107 | James | Martinez | HR | Boston
108 | Maria | Rodriguez | Engineering | New York
employee_id | first_name | last_name | department_name | location
------------|------------|-----------|-----------------|-------------
101 | John | Smith | Engineering | New York
102 | Sarah | Johnson | Engineering | New York
103 | Michael | Williams | Marketing | Los Angeles
104 | Emily | Brown | Marketing | Los Angeles
105 | David | Jones | Sales | Chicago
106 | Lisa | Garcia | NULL | NULL
107 | James | Martinez | HR | Boston
108 | Maria | Rodriguez | Engineering | New York
department_name | location | employee_count | avg_salary
----------------|--------------|----------------|------------
Engineering | New York | 3 | 88333.33
Marketing | Los Angeles | 2 | 69500.00
Sales | Chicago | 1 | 75000.00
HR | Boston | 1 | 62000.00
Finance | New York | 0 | NULL
✅ JOIN Fundamentals
- Understanding of when to use each join type
- Proper syntax for INNER, LEFT, RIGHT, and FULL OUTER joins
- Working with multiple joins in a single query
✅ Data Relationships
- One-to-many relationships (departments → employees)
- Many-to-many relationships (employees ↔ projects via junction table)
- Handling NULL values appropriately
✅ Query Optimization
- Using appropriate join types to minimize unnecessary data
- Proper use of WHERE vs HAVING clauses
- Understanding Cartesian products and avoiding them when unintended
✅ Real-World Applications
- Finding orphaned records (employees without projects)
- Aggregating data across related tables
- Generating reports from normalized data
✅ Best Practices
- Table aliasing for readability
- Clear column naming in results
- Handling edge cases (division by zero, NULL values)
- Preventing duplicate results in self joins
sql-joins-portfolio/
│
├── README.md # This file
├── schema.sql # Database creation and sample data
├── sql_joins_examples.sql # All join examples and queries
└── sample_results.txt # Expected query outputs
-
What's the difference between INNER JOIN and LEFT JOIN?
- Demonstrated in Examples 1.1 vs 2.1
-
How do you find records that don't have a match?
- LEFT JOIN with WHERE IS NULL (Example A4)
-
How do you join more than two tables?
- Multiple joins in Examples 1.2, A5
-
What is a self join and when would you use it?
- Example 6.1 (finding employee pairs)
-
How do you handle many-to-many relationships?
- Junction table pattern (employee_projects)
- Add performance benchmarks with EXPLAIN
- Include index optimization examples
- Add stored procedures using joins
- Create views for common join patterns
- Add window functions with joins
This project is open source and available for educational purposes.
Eric Joye mail@ericjoye.com | https://www.linkedin.com/in/ericjoye | https://github.com/ericjoye
Demonstrating SQL proficiency for entry-level data analyst and developer positions