Interview Handbook by Sayed Elmahdy العربية

Topic 13 · Data & databases

Relational algebra & database theory

Selection, projection, joins, how they map to SQL, the query optimizer, functional dependencies, keys.

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

15 questionsJunior · 4Mid · 7Senior · 410 quiz

Concepts

What you will learn: Break a query into clear steps: choose rows, display columns, and connect related data. This helps explain why a join duplicated an order or why an invoice total is larger than expected before you change or tune the query.

Before you start: A table has rows, and each row has values under named columns; a key is one or more columns that identify a row. Basic SQL is enough to connect the symbols to familiar commands. No advanced math is required; each symbol is explained as it appears.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#Bonus: why relational algebra matters to a .NET engineerBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

A query can be syntactically valid and still return wrong totals because of join multiplicity, duplicates or filter placement. Relational algebra gives you a small language for decomposing a query and reviewing -generated or -generated . This entire topic is optional; understand the result before debating execution speed.

A relation is a set of tuples sharing a . A tuple is a row, an attribute is a named column, and a domain is its permitted set of values. The theoretical relation has no ordering or duplicates; a SQL table can contain duplicates and NULLs. Algebra examples use set semantics; SQL uses DISTINCT where that distinction matters.

Products.IdNameCategoryPrice
1BookBooks100
2PenOffice20
3NotebookBooks60
SELECT Id, Name FROM Products ORDER BY Id; -- (1)
🧾 Code walkthrough
  1. (1) ORDER BY is a SQL presentation guarantee, not a property of a relation. Without it, do not depend on arrival order.
Use theory as a review tool: what are the input rows, output and keys? There is no need to write symbols in every pull request.

#σ, π and ρ: rows, columns and namesBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

A worked example
  1. Products has Id, Name and Price columns, with two products priced 50 and 150. We need names of products costing more than 100.
  2. Selection, σ, keeps rows satisfying the condition: only the 150-priced product remains. , π, keeps requested columns: only Name remains.
  3. Selection asks which rows; projection asks which columns. WHERE expresses the filter and SELECT the output shape, with later caveats about duplicates and .

A product screen asking only for book names should not load every row and column. Selection σ filters rows. Projection π selects attributes and removes duplicates in the algebra. Rename ρ names a relation or its attributes to remove ambiguity, especially in self joins.

ExpressionResult from Products
σ Category='Books' (Products)Rows 1 and 3, all columns
π Category (Products)Books and Office, once each
ρ P (Products)The same data named P
SELECT * FROM Products WHERE Category = 'Books';       -- (1) σ
SELECT DISTINCT Category FROM Products;               -- (2) π
SELECT P.Id AS ProductId, P.Name FROM Products AS P;   -- (3) ρ
🧾 Code walkthrough
  1. (1) WHERE corresponds to selection despite SQL's confusing SELECT keyword.
  2. (2) DISTINCT matches theoretical projection; an ordinary SELECT keeps duplicates.
  3. (3) Aliases change how names are referenced without changing stored data.

Do not discard a join key before it is needed. DISTINCT can hide a join bug; understand why duplicates appear first.

#∪, − and ∩: set operationsBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

Customers who bought this month or last month require union; those in both require intersection; those who bought previously but did not return require difference. Inputs must be union-compatible: the same number of attributes with compatible domains, renaming attributes where needed.

A(UserId)B(UserId)A ∪ BA − BA ∩ B
1, 22, 31, 2, 312
SELECT UserId FROM A UNION SELECT UserId FROM B;       -- (1)
SELECT UserId FROM A EXCEPT SELECT UserId FROM B;      -- (2)
SELECT UserId FROM A INTERSECT SELECT UserId FROM B;   -- (3)
🧾 Code walkthrough
  1. (1) UNION removes duplicates, including duplicates within an input.
  2. (2) EXCEPT is difference; direction matters: B − A yields 3.
  3. (3) INTERSECT keeps each shared row once.

Use UNION ALL when duplicates should remain; unnecessary deduplication adds work. These combine result rows, unlike a join that combines attributes. details: Combining queries.

#× and ⋈: from all pairs to joinsBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

A Cartesian product × forms every pair. Two P rows and three O rows produce six pairs. A theta join selects pairs satisfying a predicate θ; an equi-join uses equality predicates. A natural join implicitly equates all same-named attributes and keeps one copy of them, so adding another shared name can unexpectedly change results.

P(Id, Price)O(OrderId, ProductId, MaxPrice)
(1,100), (2,20)(10,1,120), (11,1,80), (12,9,30)
ExpressionResult
P × O6 pairs
P ⋈ P.Id=O.ProductId OID pairs (1,10) and (1,11)
P ⋈ P.Price<O.MaxPrice O(1,10), (2,10), (2,11), (2,12)
SELECT P.Id, O.OrderId FROM P CROSS JOIN O;                 -- (1)
SELECT P.Id, O.OrderId FROM P
JOIN O ON P.Id = O.ProductId;                              -- (2)
SELECT P.Id, O.OrderId FROM P
JOIN O ON P.Price < O.MaxPrice;                            -- (3)
-- Natural join equivalent with explicit matching names after rename:
SELECT P.Id, P.Price, O.OrderId, O.MaxPrice
FROM P JOIN O ON P.Id = O.ProductId;                       -- (4)
🧾 Code walkthrough
  1. (1) The displays IDs only; the full product contains all attributes.
  2. (2) Equality permits multiple orders for one product.
  3. (3) θ can be a non-equality comparison.
  4. (4) After renaming ProductId to Id, this matches a natural join if Id is the only shared name. Server has no NATURAL JOIN; explicit predicates are clearer and portable.

Check before SUM: a one-to-many join may repeat an order amount for every line. Do not use CROSS JOIN when a relationship predicate is required.

#Outer joins, ⋉ and ▷: matching and absenceBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

Imagine Users on the left and Orders on the right: a join connects rows when their key values match. An inner join returns matches only. A left outer join keeps every user, even without an order, and puts where matching order data is missing; here NULL means “no matching value,” not zero. Right reverses the sides, and full preserves unmatched rows from both. These extend classical algebra, which has no NULL. A semi-join ⋉ returns users who have orders without order columns or duplicate users when they have several orders. Here an anti-join ▷ returns users with no order; notation varies between references.

Users(Id)Orders(Id, UserId)Users ⋉ OrdersUsers ▷ Orders
1, 2(10,1), (11,1), (12,3)12

Left join produces (1,10), (1,11), (2,NULL). Right produces (NULL,12) instead of (2,NULL). Full produces all four.

SELECT U.Id, O.Id AS OrderId FROM Users U
LEFT JOIN Orders O ON O.UserId = U.Id;                     -- (1)
SELECT U.Id, O.Id AS OrderId FROM Users U
RIGHT JOIN Orders O ON O.UserId = U.Id;
SELECT U.Id, O.Id AS OrderId FROM Users U
FULL OUTER JOIN Orders O ON O.UserId = U.Id;
SELECT U.Id FROM Users U WHERE EXISTS
    (SELECT 1 FROM Orders O WHERE O.UserId = U.Id);         -- (2)
SELECT U.Id FROM Users U WHERE NOT EXISTS
    (SELECT 1 FROM Orders O WHERE O.UserId = U.Id);         -- (3)
🧾 Code walkthrough
  1. (1) NULL means no matching order, not order number zero.
  2. (2) EXISTS tests presence without needing DISTINCT for user 1's two orders.
  3. (3) NOT EXISTS tests absence without NOT IN's NULL trap.

Put a right-side filter in ON when unmatched left rows must survive. A subsequent WHERE O.Status = 'Paid' rejects NULLs and commonly changes the effective meaning to an inner join.

#÷ and γ: all required items and aggregationBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

Division ÷ asks “who bought every required product?” Given Bought(UserId, ProductId) and Required(ProductId), it returns users paired with every Required product. Aggregation γ belongs to extended algebra and groups rows to calculate counts or sums.

Bought(UserId, ProductId)Required(ProductId)Bought ÷ Required
(1,10), (1,20), (2,10)10, 20UserId = 1
-- Candidate users are the users occurring in Bought, as in division.
SELECT DISTINCT B.UserId FROM Bought B
WHERE NOT EXISTS (
    SELECT 1 FROM Required R
    WHERE NOT EXISTS (
        SELECT 1 FROM Bought B2
        WHERE B2.UserId = B.UserId AND B2.ProductId = R.ProductId)); -- (1)

-- γ UserId; COUNT(*)→ItemCount (Bought)
SELECT UserId, COUNT(*) AS ItemCount
FROM Bought GROUP BY UserId;                                   -- (2)
🧾 Code walkthrough
  1. (1) “There is no required product missing” expresses every. If Required is empty, all users occurring in Bought qualify. To include users with no purchases, change the candidate universe explicitly.
  2. (2) The result is (1,2) and (2,1). COUNT(*) includes duplicates; use COUNT(DISTINCT ProductId) for distinct products.

A GROUP BY/HAVING alternative must handle duplicates and an empty required set correctly. Division is not numeric division and is unnecessary for “any product,” which needs a single EXISTS.

#Equivalences and query trees: optimizer reasoningBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

If a query joins a million orders before filtering paid ones, pushing the selection below the join may reduce intermediate rows. This is a logical equivalence, not a promise of a particular physical plan. The optimizer weighs statistics, indexes and join-algorithm costs.

EquivalenceCondition
σp(σq(R)) = σp∧q(R)Ordinary predicates without side effects
σp(R ⋈ S) = (σp(R)) ⋈ Sp references only R; inner join
R ⋈ S ≡ S ⋈ RInner join preserving names, predicates and output
(R ⋈ S) ⋈ T ≡ R ⋈ (S ⋈ T)Inner joins with correctly redistributed predicates
Before:                             After:
π U.Name, O.Id                      π U.Name, O.Id
       |                                   |
σ O.Status='Paid'                         ⋈ U.Id=O.UserId
       |                                /               \
⋈ U.Id=O.UserId                       Users       σ Status='Paid'   (1)
 /           \                                          |
Users       Orders                                    Orders
SELECT U.Name, O.Id FROM Users U
JOIN Orders O ON U.Id = O.UserId
WHERE O.Status = 'Paid';                         -- (2)
🧾 Code walkthrough
  1. (1) The predicate references Orders only, so it can move below the inner join.
  2. (2) This can already receive the optimized plan; a subquery is not required to force logical evaluation order.

Do not apply these rules blindly to outer joins, LIMIT, aggregates or nondeterministic expressions. pushdown must retain keys and attributes needed by joins and predicates. Inspect the actual plan rather than judging the SQL's appearance.

#SQL bags and NULL: where direct translation breaksBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

normally uses bags: duplicates are part of the result. Prices 20, 20, 100 produce three rows with SELECT Price but only 20 and 100 with theoretical . Joins multiply multiplicities; DISTINCT at the end can hide a bug without repairing a SUM computed earlier.

is not an unknown value that equals itself. Comparisons involving it generally produce ; WHERE and HAVING keep only TRUE. NOT UNKNOWN remains UNKNOWN; FALSE AND UNKNOWN is FALSE, while TRUE OR UNKNOWN is TRUE.

ExpressionResult
NULL = NULLUNKNOWN
5 NOT IN (1, NULL)UNKNOWN
NULL IS NULLTRUE
COUNT(*) / COUNT(Price)All rows / non-NULL Price rows
SELECT Price FROM Products;                         -- (1) preserves duplicates
SELECT DISTINCT Price FROM Products;
SELECT UserId FROM A UNION ALL SELECT UserId FROM B; -- (2) preserves duplicates
SELECT Id FROM Products WHERE Price IS NULL;        -- (3)
SELECT U.Id FROM Users U WHERE NOT EXISTS (
    SELECT 1 FROM Orders O WHERE O.UserId = U.Id);    -- (4)
🧾 Code walkthrough
  1. (1) SELECT and SELECT DISTINCT are different operations.
  2. (2) UNION ALL retains every occurrence.
  3. (3) IS NULL tests missingness; = NULL does not.
  4. (4) NOT EXISTS checks for matching rows; a NULL Orders.UserId does not poison the entire result as it can with NOT IN.

Duplicate elimination treats NULLs as not distinct from each other even though NULL = NULL is not TRUE. Do not infer set-operator behavior solely from WHERE comparisons.

#Functional dependencies, closure and keysBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

Assume a table has one row per product on an order. One OrderId can appear on several rows, but those rows belong to the same user. If each OrderId has exactly one allowed UserId, write OrderId → UserId. This functional dependency is a rule about every valid version of the data: any two rows agreeing on the left must agree on the right, not just in the sample shown. The arrow does not imply the reverse or a cause-and-effect relationship. We will use these rules to find which columns distinguish one order line.

OrderIdUserIdUserEmailProductIdQty
71a@example.test102
71a@example.test201

Before the calculation: the closure of a set of columns is every column we can infer from it by repeatedly applying the rules. If its closure reaches every table column, the set is a that identifies a row; if no smaller subset does, it is a candidate key. The primary key is the candidate key chosen for the table.

R(OrderId, UserId, UserEmail, ProductId, Qty)
Assumed rules: one line per product per order.
FDs:
  OrderId → UserId
  UserId → UserEmail
  OrderId, ProductId → Qty

{OrderId}+ = {OrderId, UserId, UserEmail}                    (1)
{OrderId, ProductId}+ = all attributes of R                 (2)
🧾 Code walkthrough
  1. (1) Closure starts with the given attributes and repeatedly adds attributes implied by dependencies until nothing changes.
  2. (2) The pair reaches every attribute, making it a superkey. Neither part alone does, so it is also a candidate key under the stated rules.

A superkey identifies a tuple; a candidate key is a minimal superkey with no removable attributes; a primary key is a selected candidate. Minimal does not mean shortest among all candidates. We did not assume unique email; add that rule and recalculate if it applies.

CREATE TABLE OrderLines (
    OrderId int NOT NULL,
    ProductId int NOT NULL,
    Qty int NOT NULL,
    PRIMARY KEY (OrderId, ProductId)
);

The primary key enforces uniqueness of the pair; it does not automatically establish the other dependencies.

#Normalization from FDs, and calculus versus algebraBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

Repeating UserEmail on every order line causes update anomalies: one row changes and another is forgotten. Decompose according to dependencies, not merely because a table is large.

FormTestExample application
One domain value per attribute; no repeating groupsOne product per line instead of Product1 and Product2
1NF with no non-prime attribute dependent on a proper subset of any candidate keyMove UserId and UserEmail out of lines because OrderId alone determines them
Every nontrivial has a determinant or prime attributes on the rightUserId → UserEmail calls for a separate Users relation here
Every nontrivial FD has a superkey determinantStronger than 3NF; analyze all dependencies

A prime attribute belongs to some candidate key. With a single simple key, “no transitive dependency of non-key attributes” is useful intuition, not a substitute for the general 3NF definition.

Users(UserId PK, UserEmail)                              (1)
Orders(OrderId PK, UserId FK)
OrderLines(OrderId FK, ProductId, Qty, PK(OrderId, ProductId))

Lossless reconstruction:
Users ⋈ Orders ⋈ OrderLines                              (2)
SELECT O.OrderId, U.UserId, U.UserEmail, L.ProductId, L.Qty
FROM Orders O JOIN Users U ON U.UserId = O.UserId
JOIN OrderLines L ON L.OrderId = O.OrderId;               -- (3)
🧾 Code walkthrough
  1. (1) Each fact has one clear home under the assumed dependencies.
  2. (2) Lossless means reconstructing the original without spurious tuples. Dependency preservation also matters for local enforcement; BCNF decomposition does not always preserve every dependency.
  3. (3) reconstructs original rows with valid keys and foreign keys; arbitrary decomposition does not guarantee this.

Relational calculus describes desired tuples through predicates and quantifiers; algebra expresses compositions of operators. Safe, domain-independent calculus and classical algebra have equivalent expressive power. SQL is declarative and related to this logic, but bags, and aggregation require additional care. Algebra is not a physical execution plan imposing a fixed runtime order.

Do not denormalize before measuring a bottleneck and assigning responsibility for updating copies. Further reading: Relational algebra and normalization.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

13.1Why would a .NET developer learn relational algebra?Junior
Model answer

I use relational algebra as a compact reasoning tool for queries, not as notation I must write every day. It helps me ask which rows survive, which columns remain and whether a join multiplies rows. That is useful when reviewing -generated or an -generated report that looks plausible but returns wrong totals. I separate logical correctness from physical execution, then inspect the database plan to understand performance rather than assuming the written SQL determines execution order.

13.2What are a relation, tuple, attribute and domain?Junior
Model answer

A relation is a set of tuples with a common . A tuple is one row, an attribute is a named component of that row, and its domain defines allowed values. In the classical model, a relation has neither duplicate tuples nor an inherent row order. I distinguish that from a table or query result, which can contain duplicates and NULLs. That distinction prevents me from translating theoretical set rules into SQL without checking their assumptions.

13.3How do selection, projection and rename map to SQL?Junior
Model answer

Selection filters rows and usually maps to WHERE. selects attributes and, in set algebra, removes duplicates, so SELECT DISTINCT is the closer equivalent. Rename changes relation or attribute names and maps to table and column aliases. I point out the naming trap: SQL SELECT is mostly about projection, while algebraic selection means filtering. I also retain any attributes needed by later joins or predicates instead of projecting them away too early.

13.4What is the difference between UNION and UNION ALL?Junior
Model answer

UNION combines compatible result sets and removes duplicate rows. UNION ALL keeps every occurrence, so it better expresses concatenation when duplicates are meaningful and avoids unnecessary deduplication. Both require the same column count and compatible types. I choose based on the intended result rather than using UNION to hide duplicate-producing joins. Neither operation implies row ordering, so I add a final ORDER BY when the consumer requires a stable presentation order.

13.5How are Cartesian product, theta join and equi-join related?Mid
Model answer

A Cartesian product forms every pair of input tuples. A theta join can be expressed as a selection over that product using a predicate, such as a price comparison. An equi-join is the special case using equality. I use this model to reason about : joining one order to several lines repeats the order's columns. Summing its total after that join can overcount even though the join predicate itself is perfectly valid.

13.6Why can a natural join be fragile?Mid
Model answer

A natural join automatically matches every same-named attribute and returns one copy of each shared attribute. That is concise in algebra but fragile in evolving schemas: adding a Status column to both tables can introduce a new equality condition and change the result without changing the query. I normally prefer explicit join predicates and an explicit . I also check dialect support, because Server does not provide NATURAL JOIN syntax.

13.7When would you use a semi-join instead of an inner join?Mid
Model answer

I use a semi-join when the question is whether a matching row exists, not which matching rows exist. For users who have orders, WHERE EXISTS returns each input user without multiplying it by the number of orders. An inner join followed by DISTINCT may express the same set, but it obscures the existence requirement. For users without orders, I use NOT EXISTS, checking the correlation predicate carefully and avoiding NOT IN when nullable values could change its meaning.

13.8How can a WHERE clause accidentally change a left join?Mid
Model answer

A left join preserves unmatched left rows by filling right-side attributes with . If I then put a right-side condition such as Order.Status equals Paid in WHERE, those NULL-extended rows fail the filter and disappear. If I want every user with any matching paid orders, I put that condition in ON. If I only want users who have paid orders, an inner join or EXISTS may describe the intention more clearly. I verify both matched and unmatched examples.

13.9What does relational division express?Mid
Model answer

Division expresses a for-every requirement, such as users who bought every required product. I often translate it into double NOT EXISTS: there is no required product for which the user has no purchase. I explicitly define the candidate universe and test the empty required set, because every candidate then qualifies by vacuous truth. A GROUP BY and HAVING alternative can work, but I must handle duplicates and missing candidates correctly instead of comparing raw row counts blindly.

13.10Why is SELECT DISTINCT not always a fix for duplicate rows?Mid
Model answer

I first determine whether repeated rows are legitimate bag semantics or a join error. DISTINCT can remove meaningful occurrences and can hide a missing join predicate. It also cannot repair an already inflated by a one-to-many join. I check keys, relationship and the level at which aggregation happens. Sometimes the right solution is EXISTS or aggregating the many-side first. I only add DISTINCT when unique projected tuples are actually the required result.

13.11How does NULL affect NOT IN and COUNT?Mid
Model answer

Comparisons with generally produce , and WHERE keeps only TRUE. If a NOT IN subquery contains NULL, an otherwise unmatched value can evaluate to UNKNOWN and disappear, which is why I often use a correlated NOT EXISTS. COUNT(*) counts rows, including rows containing NULLs, while COUNT(column) counts non-NULL values of that expression. I test nullable inputs explicitly because ordinary two-valued Boolean intuition is not sufficient for predicates.

13.12When is selection pushdown valid, and when is it dangerous?Senior
Model answer

I can push a predicate referencing only one input below an inner join while preserving the result under the relevant semantics. That may reduce intermediate rows, but the physical benefit still depends on statistics and access paths. I do not apply the rule blindly to outer joins, aggregation, limits or nondeterministic expressions. I also preserve attributes needed by later predicates. The optimizer may already perform the rewrite, so I compare actual plans instead of assuming a nested subquery forces better execution.

13.13How do you compute attribute closure and identify candidate keys?Senior
Model answer

I start the closure with the proposed attributes, then repeatedly apply functional dependencies whose left sides are already included, adding their right sides until nothing changes. If the closure covers the entire , the proposed set is a . I remove attributes to test minimality; a minimal superkey is a candidate key. I derive dependencies from business rules, not just sample data, and choose a primary key from the candidates after considering stability and implementation needs.

13.14How do functional dependencies lead to normalization?Senior
Model answer

I derive keys and dependencies first. A non-prime attribute depending on part of a candidate key violates . For , each nontrivial dependency must have a determinant or prime attributes on the right; requires the determinant to be a superkey. I decompose to remove update anomalies while checking lossless reconstruction and dependency preservation. Those are separate goals, and BCNF decomposition may sacrifice preservation. I only denormalize later for a measured workload with an explicit consistency strategy.

13.15How does relational calculus differ from relational algebra?Senior
Model answer

Relational calculus describes which tuples satisfy a logical condition, using predicates and quantifiers. Relational algebra describes a composition of operations producing a relation. Safe, domain-independent calculus and classical algebra have equivalent expressive power, so they are alternative ways to describe the same class of relational queries. I would not confuse algebra with a fixed physical plan. adds practical semantics such as duplicates, and aggregation, which need separate treatment when translating theoretical equivalences.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

Which operator filters tuples by a predicate?

σ selects rows; π projects attributes.

Products has categories Books, Books, Office. What does π Category return under set semantics?

Theoretical eliminates duplicates.

R contains 3 tuples and S contains 4. How many tuples are in R × S?

Every R tuple pairs with every S tuple.

Which SQL form naturally expresses users having at least one order without multiplying users?

This is a semi-join: an existence test.

What does relational division usually express?

÷ represents a requirement for every divisor tuple.

What does WHERE keep when a predicate evaluates to UNKNOWN?

Both FALSE and reject the row.

Which is required before pushing a predicate below an inner join onto R?

The predicate must be evaluable using R alone.

A superkey with no removable attribute is called what?

A candidate key is a minimal .

Given A → B and B → C, which attributes are in {A}+?

Start with A and apply both dependencies in sequence.

What does a lossless decomposition guarantee?

Lossless reconstruction and dependency preservation are separate properties.