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.Id | Name | Category | Price |
|---|---|---|---|
| 1 | Book | Books | 100 |
| 2 | Pen | Office | 20 |
| 3 | Notebook | Books | 60 |
SELECT Id, Name FROM Products ORDER BY Id; -- (1)
- (1) ORDER BY is a SQL presentation guarantee, not a property of a relation. Without it, do not depend on arrival order.
#σ, π and ρ: rows, columns and namesBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
- Products has Id, Name and Price columns, with two products priced 50 and 150. We need names of products costing more than 100.
- Selection, σ, keeps rows satisfying the condition: only the 150-priced product remains. , π, keeps requested columns: only Name remains.
- 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.
| Expression | Result 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) ρ
- (1) WHERE corresponds to selection despite SQL's confusing SELECT keyword.
- (2) DISTINCT matches theoretical projection; an ordinary SELECT keeps duplicates.
- (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 ∪ B | A − B | A ∩ B |
|---|---|---|---|---|
| 1, 2 | 2, 3 | 1, 2, 3 | 1 | 2 |
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)
- (1) UNION removes duplicates, including duplicates within an input.
- (2) EXCEPT is difference; direction matters: B − A yields 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) |
| Expression | Result |
|---|---|
| P × O | 6 pairs |
| P ⋈ P.Id=O.ProductId O | ID 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)
- (1) The displays IDs only; the full product contains all attributes.
- (2) Equality permits multiple orders for one product.
- (3) θ can be a non-equality comparison.
- (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 ⋉ Orders | Users ▷ Orders |
|---|---|---|---|
| 1, 2 | (10,1), (11,1), (12,3) | 1 | 2 |
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)
- (1) NULL means no matching order, not order number zero.
- (2) EXISTS tests presence without needing DISTINCT for user 1's two orders.
- (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, 20 | UserId = 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)
- (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) 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.
| Equivalence | Condition |
|---|---|
| σp(σq(R)) = σp∧q(R) | Ordinary predicates without side effects |
| σp(R ⋈ S) = (σp(R)) ⋈ S | p references only R; inner join |
| R ⋈ S ≡ S ⋈ R | Inner 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)
- (1) The predicate references Orders only, so it can move below the inner join.
- (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.
| Expression | Result |
|---|---|
| NULL = NULL | UNKNOWN |
| 5 NOT IN (1, NULL) | UNKNOWN |
| NULL IS NULL | TRUE |
| 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)
- (1) SELECT and SELECT DISTINCT are different operations.
- (2) UNION ALL retains every occurrence.
- (3) IS NULL tests missingness;
= NULLdoes not. - (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.
| OrderId | UserId | UserEmail | ProductId | Qty |
|---|---|---|---|---|
| 7 | 1 | a@example.test | 10 | 2 |
| 7 | 1 | a@example.test | 20 | 1 |
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)
- (1) Closure starts with the given attributes and repeatedly adds attributes implied by dependencies until nothing changes.
- (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.
| Form | Test | Example application |
|---|---|---|
| One domain value per attribute; no repeating groups | One product per line instead of Product1 and Product2 | |
| 1NF with no non-prime attribute dependent on a proper subset of any candidate key | Move UserId and UserEmail out of lines because OrderId alone determines them | |
| Every nontrivial has a determinant or prime attributes on the right | UserId → UserEmail calls for a separate Users relation here | |
| Every nontrivial FD has a superkey determinant | Stronger 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)
- (1) Each fact has one clear home under the assumed dependencies.
- (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) 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
No results
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
Which operator filters tuples by a predicate?
Products has categories Books, Books, Office. What does π Category return under set semantics?
R contains 3 tuples and S contains 4. How many tuples are in R × S?
Which SQL form naturally expresses users having at least one order without multiplying users?
What does relational division usually express?
What does WHERE keep when a predicate evaluates to UNKNOWN?
Which is required before pushing a predicate below an inner join onto R?
A superkey with no removable attribute is called what?
Given A → B and B → C, which attributes are in {A}+?
What does a lossless decomposition guarantee?