skip to content

Why is the outer join treated as an extended relational-algebra operator rather than something derivable from the basic operators, and what does it do with tuples that find no match on the other side?

level: seniorimportance: should knowfreq 40%

answer

  1. dangling tuple = no partner, padded with null
  2. basic operators never invent values; null is invented
  3. model had no null: 3-valued logic arrives with it
  4. inner join reorders freely; outer join is not associative
  5. null-rejecting predicate after outer join = inner join

basics

~20 s

An outer join keeps dangling tuples — those with no matching partner — and pads their missing attributes with null. Basic relational algebra has no null value and no operator that invents one, so outer join needs both an extension to the value domain and an operator definition of its own.

solid answer

~60 s

An **inner join** discards any tuple that finds no partner; those are called **dangling tuples**. The outer-join family preserves them: - left outer join preserves dangling tuples of the left operand, - right outer join preserves those of the right, - full outer join preserves both, and in every case the missing side's attributes are filled with null. It is an extension for two reasons. First, the basic operators are value-preserving — they cannot manufacture a null that was not in the input. Second, the pure relational model as originally defined has no null at all, so accepting outer join means accepting three-valued logic and everything that follows from it. The practically important consequence is **algebraic**: outer joins are not freely reorderable. Inner joins are commutative and associative, so an optimizer can permute them at will. Outer join is not associative in general, and pushing a predicate on the null-supplying side below an outer join turns it into an inner join. Those restrictions constrain the whole rewrite space, which is why plans over outer joins are often worse than the equivalent inner-join plans.

code

text · 7 lines
text
Cust(id, name)          Ord(cust_id, amt)
(1, ann)                 (1, 50)
(2, bob)

Cust LEFT JOIN[id = cust_id] Ord
(1, ann, 1,    50)
(2, bob, null, null)   <- dangling tuple preserved, padded

go deeper

for a junior

Know what a dangling tuple is, what left, right and full outer joins preserve, and that missing attributes are filled with null.

for a middle

Explain why it is an extension — nulls are invented values outside the basic operators and outside the original model — and show the predicate-placement trap.

for a senior

Discuss reorderability: inner joins commute and associate, outer joins do not in general, and null-rejecting predicates are what let an optimizer downgrade an outer join to an inner one.

for a principal

Weigh outer join against modelling optionality as a separate relation, and discuss how the restricted rewrite space for outer joins shapes plan quality in wide reporting queries.

## Dangling tuples When two relations are joined on a condition, a tuple of either operand that satisfies the condition with *no* tuple of the other operand is called a **dangling tuple**. Inner join drops it. That loss is often exactly what you want and often exactly what you do not: a report of "every customer with their order total" must still show customers with zero orders; an inner join silently deletes them, and the resulting number is wrong in a way nobody notices until an audit. The outer-join family exists to preserve dangling tuples: - **Left outer join** returns all inner-join results plus, for every dangling tuple of the left operand, that tuple padded with null in all right-side attributes. - **Right outer join** is the mirror image. - **Full outer join** preserves dangling tuples from both operands. The result schema is identical to the inner join's; only the tuple set differs. ## Why it counts as an extension Two independent reasons, and a strong answer names both. **1. Value-preservation.** Selection, projection, product, union and difference never invent a value: everything in an output appeared in an input. Outer join places a null into an attribute position where no input supplied one. Like aggregation, it is therefore outside the expressive closure of the basic operators. **2. The model has no null.** Codd's original relational model is defined over relations of total tuples: every attribute has a value from its domain. Introducing null requires a new marker outside every domain, a definition of what comparisons involving it mean, and a decision about how the logic of predicates changes. The standard answer — three-valued logic with true, false and unknown, where a selection keeps only tuples for which the predicate is true — is a substantive extension of the model, not a notational convenience. Some relational purists reject outer join for exactly this reason and prefer to model optionality with a separate relation, so that absence is represented by the absence of a tuple rather than by a marker inside one. ## The consequences the interviewer is really after Once nulls are in play, several algebraic identities that optimizers rely on break down. **Inner joins reorder freely.** Inner join is commutative (`A JOIN B = B JOIN A`) and associative (`(A JOIN B) JOIN C = A JOIN (B JOIN C)`), which is the entire basis of join-order enumeration: any order produces the same relation, so the optimizer is free to choose the cheapest. **Outer joins do not.** `(A LEFT JOIN B) LEFT JOIN C` and `A LEFT JOIN (B LEFT JOIN C)` can differ, because in the first form a null-padded row from the `A/B` step is offered to the `C` predicate, while in the second form `B`'s dangling behaviour with respect to `C` is decided first. Full outer join is commutative but still not associative in general. Optimizers therefore carry explicit rules about which reorderings are valid, usually expressed with a notion of null-rejecting predicates and conflict sets, and in the absence of such analysis they simply refuse to reorder — which is why a chain of outer joins often executes in written order and performs badly. **Predicate placement changes meaning.** A predicate on the null-supplying side applied *after* a left outer join rejects the null-padded rows (any comparison with null is unknown, not true), which collapses the outer join into an inner join. The same predicate applied *inside* the join condition filters before padding and preserves the dangling rows. This is the single most common real-world outer-join bug, and it is a pure algebra issue: the two expressions are different trees, not different syntaxes for one tree. A predicate that is guaranteed to reject nulls is precisely the thing an optimizer looks for when deciding whether it may legally rewrite an outer join into an inner one — a rewrite it *wants* to perform, because the inner form reorders freely. **Duplicate and cardinality intuition shifts.** A left outer join returns at least as many tuples as the left operand has, and exactly that many when the join is on a key of the right side. Chained outer joins can still multiply rows if any join is on a non-unique attribute; "outer" preserves rows, it does not prevent fan-out. ## Relationship to other extended operators Outer join sits in the same family as grouping/aggregation and generalised projection: operators real systems cannot do without, that nevertheless step outside the value-preserving basic algebra. It also composes with them in a load-bearing way. The standard recipe for "show zero for categories with no rows" is an outer join from a complete dimension relation to the fact relation, followed by aggregation — because aggregation alone can never produce a group for a value it never saw. Reporting queries are full of exactly this pattern. ## Simulating outer join with basic operators plus null If you are permitted a null value but not an outer-join operator, you can build a left outer join as: the inner join, unioned with (the left operand minus the projection of the inner-join result back onto the left's attributes) Cartesian-producted with a one-tuple relation of nulls for the right's attributes. Writing this out is a useful whiteboard exercise, and it makes the point precisely: the derivation needs a relation of nulls supplied from outside, which is the extension. Without such a relation, no derivation exists. ## Interview framing Define dangling tuples, give the three variants, name both reasons it is an extension, and then spend most of your time on the practical consequence — reorderability and predicate placement. That last part is what separates a textbook answer from an operational one.

  • Give a concrete case where swapping the association of two left outer joins changes the answer.
    Consider A left-joined to B, then that result left-joined to C on a predicate over B's attributes. In the left-associated form, A rows that dangled against B carry nulls into the C predicate, which fails, so they survive as fully null-padded rows. In the right-associated form, B is joined to C first, so the B/C combination A sees is different, and rows that would have been produced by padding are produced differently or not at all. This is why optimizers require explicit null-rejection analysis before reordering.
  • When is it safe for an optimizer to convert a left outer join into an inner join?
    When some predicate applied above the join is null-rejecting on the null-supplying side — that is, it evaluates to unknown or false whenever those attributes are null. All padded rows would be discarded anyway, so the padding is wasted work, and the inner form is preferable because it restores free reordering. Engines detect this from simple comparison predicates and IS NOT NULL tests.

saying these in an interview costs you the question

  • Saying outer join is just an inner join with extra rows, so any rewrite that works for one works for the other
  • Assuming outer joins are associative and can be reordered like inner joins
  • Placing a filter on the null-supplying side above the join and expecting the padded rows to survive
  • Believing an outer join cannot multiply rows because it 'preserves' the left side
  • Claiming the original relational model always included nulls

context