N029-H1 Tier 3 · Intermediate · hard ecommerce · Brightlane

Return each product alongside the ID of any qualifying order line item

Part of NULL Handling in Joins and Aggregates in SQL

The problem

Brightlane's catalog team needs a list of every product alongside any order line items where the ordered quantity was greater than 1.

Write a query to return each product alongside the ID of any qualifying order line item.

Assumptions:

  • A qualifying line is one whose quantity is greater than 1.
  • Every product must appear. Products with qualifying lines contribute one row per qualifying line. Products with no qualifying lines contribute a single row with a missing order_item_id.

Output:

  • One row per product-qualifying-line pairing, plus one row per product with no qualifying lines, with columns name and order_item_id.
Schema · ecommerce 5 tables
categories
id integer
name text
parent_id? integer
products
id integer
name text
category_id integer
price numeric
stock_qty integer
attributes? jsonb
order_items
id integer
order_id integer
product_id integer
quantity integer
unit_price numeric
customers
id integer
name text
email text
city? text
country text
created_at timestamptz
is_active boolean
orders
id integer
customer_id integer
ordered_at timestamptz
status text
total_amount numeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
SELECT
  p.name,
  oi.id AS order_item_id
FROM
  products p
  LEFT JOIN order_items oi ON p.id = oi.product_id
  AND oi.quantity > 1

The shape

Moving oi.quantity > 1 into the ON clause keeps every product in the catalog while restricting which order line items are allowed to attach. A product with no qualifying line still appears, with order_item_id missing; a product with multiple qualifying lines contributes one row per line.

Clause by clause

  • SELECT p.name, oi.id AS order_item_id returns each product's name alongside the ID of any qualifying line attached on that row. When the join attaches nothing, oi.id is missing.
  • FROM products p LEFT JOIN order_items oi ON p.id = oi.product_id AND oi.quantity > 1 pairs each product with its qualifying lines. The ON clause carries two conjoined conditions: the product-to-line link, and the quantity restriction. A line attaches only when both are true; otherwise the product still appears, with every oi.* column missing. HDMI Cable 2m appears twice in the result because two separate line items qualified; the catalog's other products either contributed a single qualifying line (like Writing Clean Code) or no qualifying line at all and surface with order_item_id missing.

Why this and not WHERE oi.quantity > 1

The WHERE version would convert the LEFT JOIN back into an inner join on this predicate. Every unmatched product has a missing oi.quantity, and NULL > 1 is not true, so the row fails the filter. Products with no qualifying line — the majority of the catalog — would disappear from the report entirely. Products with no order lines at all would also disappear, for the same missing-value reason. The version in ON is the only one that preserves the full catalog.

The trap

A numeric comparison like > 1 looks safer than an equality check because there is no literal placeholder to worry about. The missing-value behavior is the same as with any other comparison: NULL > 1 returns missing, and a WHERE clause that returns missing drops the row. The unmatched rows the LEFT JOIN was specifically preserving are exactly the rows where oi.quantity is missing, so a WHERE filter on oi.quantity is guaranteed to drop them. The placement of any condition on a right-side column decides whether unmatched rows survive — ON keeps them, WHERE drops them.

You practiced moving a numeric-range condition into the ON clause — every left record appears, with matching right rows attached or a missing value when none qualify.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.