✍ 08: Advanced SQL — Exercises¶
Tip
Practice — try each question first, then expand the answer to check your reasoning. Queries use the Wedgewood Pacific (WP) tables: department, employee(employee_number, first_name, last_name, department_id, position, supervisor_id, office_phone, email_address), project(project_id, project_name, department_id, max_hours, start_date, end_date), assignment(project_id, employee_number, hours_worked).
Work through each question, then click Show answer to check yourself. Review the notes if you get stuck.
🔹 Q1. Write an ALTER TABLE statement that adds a nullable column bonus_eligible BOOLEAN to employee.¶
❓ Q2. You need to add a NOT NULL column region to department, which already has rows. Describe the three-step process (you don't need every value to be the same).¶
Show answer
1. Add the column allowing `NULL`s: `ALTER TABLE department ADD COLUMN region VARCHAR(20);` 2. Backfill every existing row with a real value: `UPDATE department SET region = 'Unassigned' WHERE region IS NULL;` 3. Now enforce the constraint: `ALTER TABLE department ALTER COLUMN region SET NOT NULL;` You can't add `NOT NULL` directly to a column on a table that already has rows with no value for that column yet.🔹 Q3. Write a PostgreSQL INSERT ... ON CONFLICT statement that inserts a new department row (department_id = 6, department_name = 'Analytics'), or updates department_name if a row with that department_id already exists.¶
Show answer
`EXCLUDED` refers to the row values that would have been inserted — that's how the `DO UPDATE` clause references the new incoming data.❓ Q4. Rewrite your Q3 statement for MySQL, using its upsert syntax.¶
Show answer
MySQL has no `MERGE` statement; `ON DUPLICATE KEY UPDATE` is its equivalent, and requires a unique or primary key on `department_id` to detect the conflict.🔹 Q5. Write a query using LEFT JOIN that lists every project along with any assignments, so that projects with zero assignments still appear (with NULL in the assignment columns).¶
Show answer
An inner join would silently drop any project that has no matching `assignment` rows; `LEFT JOIN` keeps every `project` row and fills unmatched columns with `NULL`.❓ Q6. Write a query using NOT EXISTS (an anti-join) to find every project that currently has zero assignments.¶
Show answer
The subquery is correlated — it references the outer query's `p.project_id` — and `NOT EXISTS` is true only when the subquery returns no rows at all for that project.🔹 Q7. Write a correlated subquery that lists employees who share the same position as at least one other employee (i.e., their position isn't unique in the company).¶
Show answer
Both aliases refer to the same `employee` table; the inner query's `WHERE` clause reaches out to `e1`, the outer query's current row, which is what makes the subquery correlated rather than a fixed, independent result.❓ Q8. Write a WITH RECURSIVE query that returns every employee who reports — directly or indirectly — to the employee with employee_number = 100, including a level column showing how many steps down the chain each one is.¶
Show answer
WITH RECURSIVE org_chart AS (
SELECT employee_number, first_name, last_name, supervisor_id, 1 AS level
FROM employee
WHERE employee_number = 100
UNION ALL
SELECT e.employee_number, e.first_name, e.last_name, e.supervisor_id,
oc.level + 1
FROM employee AS e
JOIN org_chart AS oc ON e.supervisor_id = oc.employee_number
)
SELECT * FROM org_chart ORDER BY level, last_name;
🔹 Q9. What is the difference between UNION and UNION ALL, and which should you prefer by default?¶
Show answer
`UNION` removes duplicate rows from the combined result; `UNION ALL` keeps every row from both queries, including duplicates. Prefer `UNION ALL` by default — deduplication requires comparing every row against every other row, which costs real performance, and matters only when you specifically need duplicates removed.❓ Q10. Write a query using EXCEPT (or MINUS on Oracle) to find department IDs that exist in department but have no matching employee in employee. Then note which widely-used database version first added this operator, if you're using MySQL.¶
Show answer
On Oracle, replace `EXCEPT` with `MINUS`; the logic is identical. **MySQL did not support `INTERSECT` or `EXCEPT` until version 8.0.31** (released October 2022) — on earlier MySQL versions, this would have to be rewritten using `NOT EXISTS` or a `LEFT JOIN ... WHERE right.column IS NULL`.🔹 Q11. Write a window-function query that ranks employees by total hours worked (summed across all their assignments) within each department, using RANK(). Show department_id, employee_number, total hours, and the rank.¶
Show answer
SELECT e.department_id, e.employee_number, e.last_name,
SUM(a.hours_worked) AS total_hours,
RANK() OVER (PARTITION BY e.department_id ORDER BY SUM(a.hours_worked) DESC) AS hours_rank
FROM employee AS e
JOIN assignment AS a ON e.employee_number = a.employee_number
GROUP BY e.department_id, e.employee_number, e.last_name
ORDER BY e.department_id, hours_rank;