Lab 06: INNER JOIN

Time: 30 minutes | Level: Foundations | DB: MySQL 8 / PostgreSQL 15

Overview

Learn INNER JOIN syntax, joining on foreign keys, multi-table JOINs, table aliases, avoiding Cartesian products, and the self-join pattern.


Step 1: Setup — Relational Schema

docker run -d --name mysql-lab -e MYSQL_ROOT_PASSWORD=rootpass mysql:8.0
for i in $(seq 1 30); do docker exec mysql-lab mysql -uroot -prootpass -e "SELECT 1" 2>/dev/null && break || sleep 2; done

docker exec mysql-lab mysql -uroot -prootpass << 'EOF'
CREATE DATABASE company;
USE company;

CREATE TABLE departments (
    dept_id    INT NOT NULL AUTO_INCREMENT,
    dept_name  VARCHAR(50) NOT NULL,
    location   VARCHAR(50),
    budget     DECIMAL(12,2),
    PRIMARY KEY (dept_id)
);

CREATE TABLE employees (
    emp_id      INT NOT NULL AUTO_INCREMENT,
    first_name  VARCHAR(50) NOT NULL,
    last_name   VARCHAR(50) NOT NULL,
    dept_id     INT,
    salary      DECIMAL(10,2),
    manager_id  INT,          -- self-referencing FK
    PRIMARY KEY (emp_id),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id),
    FOREIGN KEY (manager_id) REFERENCES employees(emp_id)
);

CREATE TABLE projects (
    project_id   INT NOT NULL AUTO_INCREMENT,
    project_name VARCHAR(100) NOT NULL,
    dept_id      INT,
    budget       DECIMAL(12,2),
    PRIMARY KEY (project_id),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);

CREATE TABLE project_assignments (
    emp_id      INT NOT NULL,
    project_id  INT NOT NULL,
    role        VARCHAR(50),
    hours       INT,
    PRIMARY KEY (emp_id, project_id),
    FOREIGN KEY (emp_id)     REFERENCES employees(emp_id),
    FOREIGN KEY (project_id) REFERENCES projects(project_id)
);

INSERT INTO departments (dept_name, location, budget) VALUES
('Engineering', 'Floor 3', 500000),
('Marketing',   'Floor 2', 200000),
('HR',          'Floor 1', 150000),
('Finance',     'Floor 4', 300000);

-- Insert managers first (no manager_id)
INSERT INTO employees (first_name, last_name, dept_id, salary, manager_id) VALUES
('Henry',  'Thomas',  1, 125000, NULL),   -- emp_id=1, Eng manager
('Grace',  'Taylor',  3,  70000, NULL),   -- emp_id=2, HR manager
('Karen',  'Harris',  2,  75000, NULL),   -- emp_id=3, Mkt manager
('Isabel', 'Jackson', 4,  88000, NULL);   -- emp_id=4, Fin manager

-- Insert regular employees
INSERT INTO employees (first_name, last_name, dept_id, salary, manager_id) VALUES
('Alice',  'Johnson', 1,  95000, 1),
('Carol',  'Davis',   1, 110000, 1),
('Eve',    'Martinez',1,  98000, 1),
('Liam',   'Clark',   1, 105000, 1),
('Bob',    'Smith',   2,  72000, 3),
('Frank',  'Anderson',2,  68000, 3),
('David',  'Wilson',  3,  65000, 2),
('Jack',   'White',   4,  92000, 4);

INSERT INTO projects (project_name, dept_id, budget) VALUES
('Cloud Migration',   1, 150000),
('Brand Refresh',     2,  80000),
('HR System Upgrade', 3,  50000),
('Budget Analytics',  4, 120000),
('API Platform',      1, 200000);

INSERT INTO project_assignments (emp_id, project_id, role, hours) VALUES
(1, 1, 'Tech Lead',    120),
(5, 1, 'Developer',     80),
(6, 1, 'Developer',     80),
(8, 5, 'Architect',    100),
(7, 5, 'Developer',     60),
(5, 5, 'Developer',     40),
(9, 2, 'Coordinator',   50),
(10,2, 'Designer',      30),
(11,3, 'Analyst',       40),
(12,4, 'Analyst',       60),
(4, 4, 'Lead',          20);
EOF

Step 2: Basic INNER JOIN Syntax

📸 Verified Output:

💡 INNER JOIN returns only rows where the join condition is satisfied in both tables. Employees without a department (dept_id IS NULL) would not appear.


Step 3: Table Aliases

💡 Table aliases (e, d) make queries shorter and are required when joining a table to itself. INNER keyword is optional — plain JOIN defaults to INNER JOIN.


Step 4: JOIN with WHERE and Aggregates

📸 Verified Output (aggregated):


Step 5: Multi-Table JOIN

📸 Verified Output:


Step 6: Avoid Cartesian Products

📸 Verified Output:


Step 7: Self-Join Pattern

📸 Verified Output:

💡 Self-join requires two different aliases for the same table (e for employee, m for manager). Managers themselves don't appear in the result because their manager_id is NULL — INNER JOIN excludes them.


Step 8: Capstone — Full Business Query

Find all employees, their projects, total hours committed, and department budget remaining:

📸 Verified Output:

Cleanup:


Summary

Concept
Syntax
Notes

INNER JOIN

FROM a JOIN b ON a.id = b.a_id

Only matching rows

Table alias

FROM employees e

Required for self-joins

INNER keyword

Optional

JOIN = INNER JOIN

Multi-table

Chain multiple JOINs

Order matters for readability

Self-join

FROM t AS a JOIN t AS b ON a.mgr = b.id

Same table, different aliases

Cartesian product

Missing ON clause

Avoid: rows multiply

JOIN + WHERE

Filter after joining

WHERE limits final result

JOIN + GROUP BY

Aggregate joined data

Combine patterns freely

Next: Lab 07 — Outer Joins

Last updated