Why JOINs Exist
Consider: youremployee table stores department_id, but not the department name. To display employee names alongside their department names you must connect the two tables.
Notice that Gopal Krishna disappears — they have no matching department. That is exactly
INNER JOIN behavior, and understanding when that is correct (versus using LEFT JOIN) is the core skill of this page.
The Sample Tables
department
employee (abbreviated)
JOIN Syntax
ON clause specifies the matching condition — almost always a primary key on one side and a foreign key on the other. Use table aliases (short names like e and d) to keep your queries readable, especially when joining multiple tables.
INNER JOIN
AnINNER JOIN returns only the rows that have a matching value in both tables. Rows with no match on either side are excluded.
department_id that exists in the department table. Gopal Krishna (NULL department) is excluded. Any department with no employees is also excluded.
INNER JOIN with WHERE and ORDER BY
Practice: HR employees
Write a query that returns the name and salary of all employees in the Human Resources department, sorted by salary descending.Solution
Solution
LEFT JOIN
ALEFT JOIN returns all rows from the left (first) table, plus the matching rows from the right table. Where there is no match, the right table’s columns are filled with NULL.
department_name shows as NULL.
Find Unassigned Employees
LEFT JOIN + IS NULL is the standard pattern for finding rows in the left table that have no match in the right table.
Practice: Departments with no employees
Write a query that lists department names that currently have no employees. (Hint: join fromdepartment to employee and look for NULL on the employee side.)
Solution
Solution
RIGHT JOIN
ARIGHT JOIN returns all rows from the right (second) table, plus matching rows from the left. It is the mirror of LEFT JOIN.
NULL in the employee columns where there is no match.
SQLite does not natively support
RIGHT JOIN. You can achieve the same result by swapping the table order and using a LEFT JOIN:FULL OUTER JOIN
AFULL OUTER JOIN returns all rows from both tables. Rows with matches are merged; unmatched rows on either side appear with NULL for the other table’s columns.
SQLite does not support
FULL OUTER JOIN directly. Emulate it by combining a LEFT JOIN and a RIGHT JOIN (rewritten as LEFT JOIN) with UNION ALL, then deduplicating:Joining Three or More Tables
You chain multipleJOIN clauses. Each new JOIN adds one more table to the result.
Self-JOIN
A self-join connects a table to itself. A common use case is a management hierarchy — where each employee row has amanager_id that references another employee_id in the same table.
e and m) so SQL can distinguish which “copy” of the table each reference refers to.
The Cartesian Product Trap
If you forget theON clause, SQL returns the Cartesian product — every row in the left table paired with every row in the right table. For tables with 25 and 5 rows, that is 125 rows of meaningless combinations.
Choosing the Right JOIN
INNER JOIN
Use when you only want rows that have a match on both sides. Excludes unmatched rows entirely.“Show me employees who have a department.”
LEFT JOIN
Use when you want every row from the left table, whether or not it has a match. Unmatched right columns become NULL.“Show me all employees, with their department if they have one.”
RIGHT JOIN
Use when you want every row from the right table. Equivalent to swapping the table order in a LEFT JOIN.“Show me all departments, with their employees if any exist.”
FULL OUTER JOIN
Use when you need every row from both sides regardless of matches. Useful for data reconciliation.“Show me all employees and all departments, matched where possible.”
Complete Practice: Departmental salary report
Write a query that returns:department_name, the count of employees in each department, and the average salary — but only for departments that have at least one employee. Sort by average salary descending.
Solution
Solution
INNER JOIN here naturally excludes departments with no employees. If you want to include zero-headcount departments, switch to LEFT JOIN and change HAVING COUNT(e.employee_id) >= 1 to >= 0.Next: Advanced SQL
Go beyond the basics — write subqueries, CTEs, window functions, CASE expressions, and learn how indexes and views work.