skip to content

questions

4

The relational model defines a relation as a set of tuples, yet a SQL table can hold two rows identical in every column. What is the difference between set semantics and bag (multiset) semantics, and what practical consequences follow from SQL working with bags?

level: juniorimportance: must knowfreq 55%

answer

  1. relation = set, table = bag
  2. bag keeps multiplicity
  3. dedup costs a sort or hash
  4. projection does not shrink a bag
  5. declared key = set by construction

basics

~20 s

A set holds each element at most once; a bag (multiset) also records how many copies it has. Relations are sets, but SQL tables and results are bags, so identical rows can repeat. Removing duplicates is an explicit extra step that costs work.

solid answer

~50 s

In the relational model a relation is a set of tuples: no duplicates, no ordering. That is why every relation has a key - if no two tuples are identical, the full attribute list is at worst a superkey. SQL relaxed this. A SQL table is a bag: rows may repeat and every operator preserves multiplicity. Projecting away columns does not collapse repeats, joining multiplies them, and concatenating two results adds their counts. Duplicate elimination happens only when asked for, because it costs a full sort or hash of the intermediate result, and because duplicates carry information - you cannot sum order amounts if equal amounts were silently collapsed. Consequences I watch for: result sizes grow in ways set-thinking does not predict; aggregates over duplicated rows are inflated; comparing two query results means comparing multisets, not sets; and a table with no declared key has no value-based way to address a single row.

code

text · 5 lines
text
bag A: (1,'x'), (1,'x'), (2,'y')     -> 3 rows
set  A: (1,'x'), (2,'y')             -> 2 rows

A concatenated with B{(2,'y')}       -> 4 rows (multiplicities added)
A set-unioned with B{(2,'y')}        -> 2 rows (duplicates removed)

go deeper

for a junior

Say plainly: a set has no duplicates, a bag counts them; SQL results are bags, so identical rows can appear and you must ask for duplicates to be removed.

for a middle

Add why SQL chose bags - dedup costs a sort or hash, and aggregation needs the repeats - and name the operators that multiply or add multiplicities.

for a senior

Frame it operationally: duplicates change result cardinality, inflate aggregates, and a needed dedup step usually signals join fan-out worth fixing at the source.

for a principal

Discuss where set semantics should be enforced - declared keys make a table a set once and enable optimizer proofs, versus per-query dedup which taxes every read and masks defects.

## Sets and bags A set is an unordered collection where an element is present or absent - 'present twice' is not expressible. A bag (multiset) is also unordered, but each element carries a multiplicity: how many copies it holds. As bags, {a, a, b} and {a, b} are different collections; as sets they are the same. ## The model uses sets Codd defined a relation as a set of tuples drawn from attribute domains. Three things follow. First, duplicate tuples cannot exist, so every relation has at least one candidate key - in the worst case all attributes together. Second, there is no row order and no row identity beyond the values themselves: you address a tuple only by its values. Third, the algebra is closed over sets - projecting a relation onto some attributes yields a relation, which means duplicate rows produced by dropping columns simply disappear. ## SQL chose bags SQL tables and query results are bags. There are three reasons. (1) Cost: eliminating duplicates requires comparing every row against every other, which in practice means sorting or hashing the entire intermediate result. Doing that after each projection would be a permanent tax paid mostly for nothing. (2) Information: duplicates are the raw material of aggregation. If projecting 'amount' out of an orders table collapsed equal amounts, summing or averaging them would be impossible. (3) Reality: a table with no key constraint physically can contain identical rows, and engines identify rows by a physical address (row id, tuple id), not by value - so the storage layer is a bag regardless. Concretely, SQL operators are multiset operators: a scan returns each stored row once, a filter removes rows but never merges them, a projection keeps multiplicity, a join produces one output row per matching pair (multiplying multiplicities), and a concatenating union adds them. Only an explicit duplicate-eliminating request, a set-flavoured union, or grouping converts a bag back into a set. ## Where the difference bites **Result size.** Projecting to fewer columns never shrinks a bag, so 'select just the customer id' from a million-row order table still returns a million rows, not one per customer. **Aggregate correctness.** Counting rows is not counting entities. If a row has been duplicated by a join, sums and averages are inflated, and averages are skewed in a way that no scaling factor fixes. **Result comparison.** Two queries are equivalent only if they produce the same multiset. Test assertions and data-diff tooling that compare 'the same rows' while ignoring counts will pass on genuinely different results. **Optimization.** A rewrite that is valid over sets may change multiplicities, so the optimizer must reason about duplicate preservation, not just membership. ## Getting set semantics back The robust way is a declared key or unique constraint: it makes the table a set by construction, is enforced on every write, and lets the planner prove that a duplicate-elimination step is unnecessary and remove it. The per-query way is explicit duplicate elimination, which is correct but pays sort or hash cost on every read and often hides a fan-out bug rather than fixing it. Practical rule: give every table a key, treat a required duplicate-removal step as a signal to investigate, and always know whether you are counting rows or counting entities.

  • If relations are sets, why does the relational model say every relation has a candidate key?
    Because no two tuples in a set can be identical, the complete list of attributes always distinguishes every tuple, so it is a superkey. Any minimal subset of it that still distinguishes all tuples is a candidate key. A real SQL table without a declared key breaks this guarantee, which is exactly why keyless tables cause trouble.
  • Give a case where duplicate rows are meaningful data rather than a defect.
    Event and measurement tables: two temperature readings of 21.5 from different sensors, or two identical line items on an invoice for two units of the same product. Collapsing them would destroy counts and sums. The distinction is whether the duplicate represents a separate real-world occurrence or an accidental repetition of the same fact.

A set is a guest list - a name is on it or not. A bag is the turnstile counter at the door: it records that the same person walked through four times, which is exactly what you need to bill the bar tab.

saying these in an interview costs you the question

  • Saying SQL tables are sets and therefore cannot contain duplicate rows
  • Assuming that selecting fewer columns automatically collapses repeated values
  • Treating duplicate elimination as free or as a cosmetic formatting step
  • Claiming duplicates are always a data-quality bug, so aggregation never needs them

context

open as a page

What work does a database engine actually have to do to eliminate duplicate rows from a result, and when can the planner skip that work entirely?

level: middleimportance: must knowfreq 55%

basics

~20 s

It must compare every row against every other, which in practice means sorting the whole input or building a hash table over all output columns - memory-hungry and possibly spilling to disk. It can be skipped only when a key or constraint proves the rows are already unique.

open as a page

A reporting query summed order totals correctly until a second table was joined in; now the total is roughly double, though no data changed. Explain what happened in terms of row multiplicity, and describe how you would fix it without simply adding a duplicate-removal step.

level: seniorimportance: must knowfreq 60%

basics

~20 s

The join is a bag operation: each parent row is emitted once per matching child row, so its total is added several times. Fix it by aggregating the child side first and joining that, or by using an existence check when you only need filtering - not by removing duplicates afterwards.

open as a page

A table is supposed to hold exactly one row per (customer, product) pair, but duplicates keep appearing. How would you decide whether duplicate elimination belongs in the schema, in the write path, or in every read - and what does each choice cost?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Prefer the schema: a unique constraint makes the table a set on every write, is race-proof, and lets the planner skip duplicate removal. Write-path logic alone races; read-time deduplication taxes every query and hides the defect. Read-time dedup is right only when duplicates are legitimate events.

open as a page