N021-H1 Tier 2 · Core SQL · hard ecommerce · Brightlane

Return the name of each category in every sibling pair

Part of Self-Joins in SQL

The problem

Brightlane's category team wants to identify all pairs of sibling categories — categories that share the same parent.

Write a query to return the name of each category in every sibling pair.

Assumptions:

  • The categories table contains every defined category in the catalogue.
  • Siblings share the same parent_id. A category is not its own sibling, so the result must exclude pairs where both rows refer to the same category.
  • Each pair appears twice in the result — once with category A on the left and B on the right, once with B on the left and A on the right.

Output:

  • One row per ordered sibling pair, with columns category_name and sibling_name.
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
  c1.name AS category_name,
  c2.name AS sibling_name
FROM
  categories c1
  JOIN categories c2 ON c1.parent_id = c2.parent_id
WHERE
  c1.id <> c2.id

The shape

Two categories are siblings when they share a parent_id, so the join condition pairs rows on that shared attribute (c1.parent_id = c2.parent_id) rather than on the usual primary-key link. The inequality c1.id <> c2.id then strips out the self-pairs, where the same category matches itself.

Clause by clause

  • SELECT c1.name AS category_name, c2.name AS sibling_name returns one name from each aliased instance. Each output row is an ordered pair of sibling names: Phones / Tablets, Phones / Laptops, and so on.
  • FROM categories c1 reads categories as the left side of the pair.
  • JOIN categories c2 ON c1.parent_id = c2.parent_id joins on the shared parent_id. Every Electronics child matches every Electronics child, including itself; every Clothing child matches every Clothing child, including itself.
  • WHERE c1.id <> c2.id drops the diagonal — the rows where a category matched itself. Phones-and-Phones is gone, but Phones-and-Tablets and Tablets-and-Phones both remain, which is why each pair appears twice as ordered pairs in the result.

Why this and not a join on id

Most self-joins so far have used the primary-key form: e.manager_id = m.id or c.parent_id = p.id. That form expresses a directed relationship — one row points to another by storing its ID. Siblings are not a directed relationship. Neither sibling stores a pointer to the other; they only share a third value (parent_id). The join condition has to match on that shared value directly. The general rule: when the relationship is "share an attribute" rather than "reference each other," the join condition is c1.col = c2.col on the shared attribute, not a foreign-key-to-primary-key link.

The trap

Without WHERE c1.id <> c2.id, every category pairs with itself in the result — Phones-and-Phones, Tablets-and-Tablets, every row. The join condition c1.parent_id = c2.parent_id is reflexive: any row's parent_id equals its own parent_id, so each row matches itself before it matches any genuine sibling. The fix is the explicit inequality on the primary key, which excludes the diagonal of self-matches without disturbing the cross-pairs. Anytime a self-join's ON condition would be true when both aliases land on the same row, the WHERE clause needs an inequality to drop those self-pairs.

You practiced a self-join where the join condition is on a non-primary-key column shared by both aliases (parent_id), with an inequality on id to exclude self-pairs. The recurring shape any time "things that share an attribute but aren't the same thing" is the question.

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.