Brightlane's catalogue team is cross-checking products against the category table.
Write a query to return the product name for every product whose category_id does not resolve to any row in categories.
Assumptions:
- A product with no resolving category has a missing
category_idor a value that doesn't appear ascategories.id. - The result returns one row per such orphan product.
Output:
- One row per orphan product, with a single column
product_name.
Schema · ecommerce 5 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
SELECT
p.name AS product_name
FROM
products p
FULL OUTER JOIN categories cat ON p.category_id = cat.id
WHERE
cat.id IS NULL The shape
The outer join produces three categories of rows: matched products, orphan products with the categories side NULL, and orphan categories with the products side NULL. WHERE cat.id IS NULL keeps only the orphan-product rows — products whose category_id didn't resolve to any row in categories. For Brightlane that's three rows: Gift Card $50, Gift Card $100, and Mystery Bundle.
Clause by clause
SELECT p.name AS product_namereturns just the product name. Everything else the join carried is scaffolding for the filter; only this column makes it into the output.FROM products p FULL OUTER JOIN categories cat ON p.category_id = cat.idis the reconciliation. For matched productscat.idresolves to a real value; for orphan products the join still keeps the row and fills the categories side withNULL; for orphan categories the join keeps the row and fills the products side withNULL.WHERE cat.id IS NULLis the anti-join filter. It runs after the join produces its row set and keeps only the rows where the categories side came back empty. Matched rows have a realcat.id, so they drop out. Orphan-category rows have a realcat.idtoo (and aNULLproduct name), so they drop out — which means theproduct_namecolumn on every surviving row is real and notNULL.
Why this and not a LEFT JOIN
A LEFT JOIN between the same two tables, anchored on products, would return exactly the same three rows after the same WHERE cat.id IS NULL filter. For a one-sided anti-join, LEFT JOIN is the more natural tool; it preserves the products side and discards anything else the join could have produced. The FULL OUTER JOIN form is doing extra work — keeping orphan categories the filter is about to throw away — but for this single-side question, both shapes land at the same answer.
You practiced isolating one side's orphans from a FULL OUTER JOIN (or equivalent LEFT JOIN). The recurring shape: after the outer join, WHERE right.key IS NULL keeps only the rows where the right side had no match.