skip to content

Relational division is not a primitive operator. Show how it is derived from projection, Cartesian product and set difference, and explain why the derivation is naturally read as 'build the counterexamples and subtract them'.

level: seniorimportance: nice to knowfreq 25%

answer

  1. candidates x B - A = missing pairs
  2. project missing -> disqualified candidates
  3. candidates - disqualified = answer
  4. for all = not exists ... not in
  5. non-monotone in divisor => difference required

basics

~20 s

Take all candidates (project the dividend onto the non-divisor attributes), pair every candidate with every divisor value via Cartesian product, subtract the pairs that actually exist in the dividend — what remains are the missing pairs. Project those onto the candidate attributes to get the disqualified candidates, and subtract them from all candidates.

solid answer

~60 s

With dividend `A(X, Y)` and divisor `B(Y)`: ``` candidates = pi X (A) all_pairs = candidates x B missing = all_pairs - A bad = pi X (missing) result = candidates - bad ``` Read it as a double negation. The condition "for every y in B, (x, y) is in A" is logically equivalent to "there is no y in B with (x, y) not in A". The derivation builds exactly that inner witness: `all_pairs - A` is the set of *required pairs that are absent*, and any candidate appearing there has a counterexample and is disqualified. Subtracting the disqualified candidates from all candidates leaves those with no counterexample. The important structural point is that **difference is unavoidable**. Division is non-monotone in its divisor — adding a tuple to `B` can remove results — and no combination of selection, projection, product and union is non-monotone. That proves negation must appear, and it explains why the fast implementation of a universal query is a doubly nested NOT EXISTS rather than a join.

code

text · 5 lines
text
candidates = pi X (A)
all_pairs  = candidates x B
missing    = all_pairs - A
bad        = pi X (missing)
result     = candidates - bad

go deeper

for a junior

Reproduce the five-step recipe and trace it on a small example; knowing the shape is enough at this level.

for a middle

Explain each step as a piece of the quantifier duality 'for all' equals 'not exists ... not', and get the final subtraction right.

for a senior

Add the monotonicity proof that difference is unavoidable, and compare the cost profiles of the derivation, the nested negated existential and the count comparison.

for a principal

Discuss where the candidate set legitimately comes from, the semantics that choice implies for empty divisors, and when a universal check belongs in the database at all versus in application logic.

## The setup Let the dividend be `A` with attributes `(X, Y)` and the divisor `B` with attributes `(Y)`. The goal is `A DIVIDE B`, the set of `X` values that are paired in `A` with **every** `Y` value in `B`. ## The derivation, step by step ``` 1. candidates = pi X (A) -- every x that appears at all 2. all_pairs = candidates x B -- every (x, y) the query demands 3. missing = all_pairs - A -- demanded pairs that do not exist 4. bad = pi X (missing) -- candidates with at least one missing y 5. result = candidates - bad -- candidates with none missing ``` Work it on the enrolment example. `A = Enrolled(student, course)` with `(ann, db), (ann, os), (ann, ml), (bob, db)`; `B = Required(course)` with `(db), (os)`. - `candidates = {ann, bob}` - `all_pairs = {(ann, db), (ann, os), (bob, db), (bob, os)}` - `missing = all_pairs - A = {(bob, os)}` — note `(ann, ml)` never enters, because `all_pairs` only contains required courses - `bad = {bob}` - `result = {ann}` That is the correct answer, and the trace shows why extras such as `ann`'s `ml` course are irrelevant: they never appear in `all_pairs`, so they can never create a missing pair. ## Why 'build the counterexamples and subtract them' The logical statement being computed is universal: ``` for every y in B: (x, y) in A ``` Standard quantifier duality rewrites this as ``` not ( exists y in B: (x, y) not in A ) ``` Every line of the derivation matches a piece of that formula. `candidates x B` enumerates the `y in B` for each `x` — the existential's search space. `- A` is the inner negation `(x, y) not in A`. The projection is the existential quantifier: an `x` appears in `bad` if there is *at least one* witness. The final subtraction is the outer negation. This is why the fastest practical implementation of a division-shaped query is a doubly nested negated existential rather than the literal algebraic recipe: the algebra materialises the entire candidate-by-divisor product, while a nested-negation evaluation can abandon a candidate at its first missing value. ## Why difference must appear This is the part that separates a memorised recipe from understanding. Selection, projection, Cartesian product, join and union are all **monotone**: adding tuples to any input can only add tuples to the output, never remove any. Division is *not* monotone in its divisor — take the example above, add `(ml)` to `Required`, and `ann` disappears from the result. A non-monotone function cannot be built from monotone building blocks, so any correct derivation of division must use set difference (or some other negation) at least once. The proof is short and it is a good thing to be able to give on demand. The same argument explains a family of related facts: universal quantification, "not exists", anti-join and set difference are all faces of the same non-monotone capability, and a query language without any of them cannot express "for all". ## The candidate set is a real choice Step 1 uses `pi X (A)` as the candidate set, which means a student who appears nowhere in `Enrolled` cannot be in the result. That is usually right when the dividend is the only source of candidates. But if you have a separate `Students` relation and the requirement set is empty, the two formulations diverge: dividing yields only the students who appear in `Enrolled`, whereas a natural-language reading of "students who satisfy all zero requirements" would include everyone. Whenever candidates exist independently of the dividend, take the candidate set from the candidate relation and use `Students x B - A` in step 3. Interviewers who know the operator well often probe exactly this. ## Cost profile of the three standard implementations 1. **The algebraic derivation.** Materialises `|candidates| x |B|` pairs. Simple to reason about, worst asymptotics, and it is the one to avoid at scale. 2. **Nested negated existential.** Evaluates per candidate and stops at the first missing value; with an index on `(x, y)` this is usually the winner and it degrades gracefully. 3. **Count comparison.** Restrict the dividend to pairs whose `y` is in `B`, count distinct `y` per `x`, keep those whose count equals `|B|`. Reads well and vectorises nicely, but it always processes the full restricted dividend, and it is only correct with the restriction and the distinctness — a duplicated `(x, y)` pair or an unrestricted count silently over-counts and admits candidates that should fail. All three compute the same relation. Which one an engine picks depends on selectivity, indexing and whether the divisor is small and known in advance. ## Interview framing Write the five lines, trace one small example, then state the two insights: the derivation is quantifier duality made concrete, and difference is provably required because division is non-monotone in the divisor. Finish with the observation that the algebraic form is the slowest of the three real implementations.

  • Prove that division cannot be expressed without set difference.
    Selection, projection, product, join and union are monotone: adding tuples to an input never removes tuples from the output, and monotonicity is closed under composition. Division is not monotone in its divisor — adding a required course can remove a qualifying student. Therefore no composition of the monotone operators equals division, and some non-monotone operator such as set difference must appear.
  • Where does the candidate set come from, and when does that choice change the answer?
    The textbook derivation projects the dividend, so only entities already appearing in the dividend can qualify. If candidates exist independently — a Students relation separate from Enrolled — you should build the product from that relation instead. The two versions differ whenever a candidate has no dividend tuples at all, most visibly when the divisor is empty.

Checking a packing list: instead of verifying every item is in the bag, you list everything that should be there, cross off what is present, and anyone with anything left uncrossed fails.

saying these in an interview costs you the question

  • Forgetting the final subtraction and returning the disqualified candidates as the answer
  • Subtracting A from the dividend rather than from the candidate-by-divisor product
  • Claiming the derivation works with only projection, product and join, without any difference
  • Assuming the algebraic derivation is how engines evaluate universal queries in practice
  • Using a plain count comparison without restricting to divisor values or deduplicating pairs

context