Set Operators, Views, Indexes & Joins in SQL
Press Next → or use ← → arrow keys
The Story — Two Guest Lists, One Wedding
Set operators stack results vertically (row on row, same columns). Joins stitch results horizontally (columns from one table beside another, matched by a key).
The Sample Database
online_buyers
| id | name |
|---|---|
| 1 | Asha |
| 2 | Bilal |
| 3 | Chen |
| 4 | Diya |
store_buyers
| id | name |
|---|---|
| 3 | Chen |
| 4 | Diya |
| 5 | Esha |
| 6 | Farid |
employees
| id | name | dept | mgr | sal |
|---|---|---|---|---|
| 101 | Asha | 10 | NULL | 95k |
| 102 | Bilal | 10 | 101 | 62k |
| 103 | Chen | 20 | 101 | 58k |
| 104 | Diya | 30 | 102 | 47k |
| 105 | Esha | NULL | 102 | 51k |
departments
| dept | name |
|---|---|
| 10 | Engineering |
| 20 | Sales |
| 30 | Marketing |
| 40 | Research ← 0 staff |
Asha's NULL manager, Esha's NULL dept, and Research with no employees — these expose exactly how INNER and OUTER joins differ.
Set Operators — The Venn Picture
Swap the two queries around UNION or INTERSECT and the answer is identical. Swap them around EXCEPT and you get a completely different set. Read EXCEPT as "keep the left, subtract the right."
UNION · INTERSECT · EXCEPT in Action
SELECT cust_id, name FROM online_buyers
UNION
SELECT cust_id, name FROM store_buyers
ORDER BY cust_id;
SELECT cust_id, name FROM online_buyers
INTERSECT
SELECT cust_id, name FROM store_buyers;
SELECT cust_id, name FROM online_buyers
EXCEPT
SELECT cust_id, name FROM store_buyers;
Oracle uses MINUS where standard SQL uses EXCEPT — same meaning.
Set Operator Rules at a Glance
| Operator | Returns | Removes dupes? | Order matters? | Oracle |
|---|---|---|---|---|
| UNION | All rows from both | Yes | No | UNION |
| UNION ALL | All rows from both | No | No | UNION ALL |
| INTERSECT | Rows in both | Yes | No | INTERSECT |
| EXCEPT | Left minus right | Yes | Yes | MINUS |
If duplicates are impossible or wanted, UNION ALL skips the expensive dedup pass — measurably faster.
Views — A Saved Query That Acts Like a Table
A view is a window onto your data, not a copy. You name a query once; every SELECT from the view runs it fresh against the live base tables.
CREATE VIEW staff_dirs AS
SELECT e.name, d.dept_name, e.salary
FROM employees e JOIN departments d ON e.dept_id = d.dept_id;
SELECT name, dept_name FROM staff_dirs WHERE salary > 50000; -- query it like a table
Security (expose safe columns), simplicity (wrap a complex join), consistency (one definition, one source of truth).
Standard = stores nothing, always fresh. Materialized = stores the result, must refresh — trades freshness for speed.
Indexes — The Speed Mechanism
An index is the index at the back of a textbook: instead of reading all 900 pages, you jump straight to the right one. The engine uses a sorted B-tree instead of scanning every row.
CREATE INDEX idx_emp_dept ON employees(dept_id); -- simple
CREATE INDEX idx_emp_dept_sal ON employees(dept_id, salary); -- composite
CREATE UNIQUE INDEX idx_emp_email ON employees(email); -- unique
Indexes Are Not Free
Each index must be updated on every INSERT, UPDATE and DELETE, and it eats disk. Indexing every column "to be safe" slows writes and wastes storage — index what you filter, join, or sort on.
An index on (dept_id, salary) serves queries on dept_id alone ✓ and dept_id AND salary ✓ — but salary alone ✗. The leftmost column must be used.
Joins — Stitching Tables Side by Side
A join matches rows from two tables on a related column and merges them into one wider row. Four flavours differ only in which unmatched rows they keep.
To find rows with no match — departments with no employees, customers with no orders — LEFT JOIN then filter WHERE right.key IS NULL.
INNER JOIN — Only Matching Rows
SELECT e.name, d.dept_name
FROM employees e INNER JOIN departments d ON e.dept_id = d.dept_id;
-- Asha·Eng, Bilal·Eng, Chen·Sales, Diya·Marketing (Esha & Research dropped)
Esha's NULL dept has nothing to match; Research has no employees. INNER JOIN keeps only the solid green links.
OUTER JOINs — Keep the Unmatched
Outer joins preserve rows with no match, filling the missing side with NULL.
-- LEFT: all employees, dept if any
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.dept_id;
-- RIGHT: all departments, staff if any
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d
ON e.dept_id = d.dept_id;
FULL OUTER JOIN keeps every row from both sides, NULL-filling wherever a match is missing on either side — Esha and Research both survive.
SELF JOIN — A Table Joined to Itself
A manager is an employee — manager_id points to another emp_id in the same table. So the table plays two roles at once.
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;
-- Asha→NULL, Bilal→Asha, Chen→Asha, Diya→Bilal, Esha→Bilal
Because the same table appears twice, each instance needs a distinct alias (e, m) — otherwise the database can't tell which copy a column means.
Joins vs Set Operators — Side by Side
| Property | Set Operators | Joins |
|---|---|---|
| Direction | Vertical — stack rows | Horizontal — widen rows |
| Columns | Same count & types both sides | Any columns; matched on a key |
| Result width | Same as input columns | Sum of both tables' columns |
| Matches on | Whole-row equality | A specified ON condition |
| Typical question | "In which list(s)?" | "What related detail?" |
Set operators answer membership ("who's on which list?"); joins answer relationship ("what detail connects to this row?").
Golden Rules
Four Tools, One Toolbox
You can now stack rows with set operators, widen them with any join, hide complexity behind a view, and accelerate lookups with the right index — while dodging the union-compatibility, EXCEPT-direction and left-prefix traps. Next up: transactions, concurrency and ACID.
Set operators stack rows vertically; joins widen rows horizontally; views hide the query; indexes skip the scan.
🔗 End of tutorial · Press ← to review, or click Restart