What this quiz covers
This quiz focuses on Left Join, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
A database contains four projects. Project P1 has two tasks, P2 has no tasks, P3 has three tasks, and P4 has no tasks. There is also one task whose project_id is NULL. The query is:
SELECT p.project_id, t.task_id FROM projects p LEFT JOIN tasks t ON p.project_id = t.project_id;
How many rows does the query return?
SQL Quiz
Practice Left Join in SQL with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.
This quiz focuses on Left Join, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.
A database contains four projects. Project P1 has two tasks, P2 has no tasks, P3 has three tasks, and P4 has no tasks. There is also one task whose project_id is NULL. The query is:
SELECT p.project_id, t.task_id FROM projects p LEFT JOIN tasks t ON p.project_id = t.project_id;
How many rows does the query return?
project_id is the key trap. Because the join condition is p.project_id = t.project_id, and NULL never equals anything — including another NULL — that orphaned task matches no project row and is completely excluded from the result. Choice C falls for this trap, incorrectly adding an extra row for the NULL-project task and arriving at eight.
Choice A is wrong because it ignores the taskless projects (P2 and P4), which do contribute rows in a LEFT JOIN — that's the whole point of LEFT vs. INNER JOIN. Choice D reflects a common misconception that a LEFT JOIN returns exactly one row per left-side record regardless of matches; in reality, each match generates its own row, so a project with three tasks produces three rows.
Study tip: Always remember that NULL = NULL is false in SQL join conditions — orphaned records on the right side vanish in a LEFT JOIN, while orphaned records on the left side survive with NULLs.The column payments.payment_id is a non-NULL primary key, while payments.amount is nullable. An analyst needs the IDs of accounts that have no payment records at all.
Which predicate correctly completes this anti-join query?
SELECT a.account_id FROM accounts a LEFT JOIN payments p ON a.account_id = p.account_id WHERE <span class="fill-in-blank"> </span>;
p.payment_id IS NULL, because only a NULL-extended row lacks the non-NULL payment key (correct answer)p.amount IS NULL, because a missing payment and a payment without an amount are equivalenta.account_id IS NULL, because unmatched rows set the preserved account identifier to NULLp.payment_id <> 0, because valid payment identifiers indicate that no payment is missingLEFT JOIN, SQL preserves every row from the left table and fills in NULL for all right-table columns whenever no match exists. An anti-join exploits this: you filter specifically for those NULL-filled rows to find left-table records with no matching right-table record.
The key question is: which column do you test for NULL? You want a column that is guaranteed non-NULL whenever a real match exists. That's exactly what a primary key gives you — payment_id is defined as non-NULL, so it can only appear as NULL in your result set when the row was NULL-extended (i.e., no payment matched). Testing p.payment_id IS NULL therefore unambiguously identifies accounts with zero payment records, confirming A as correct.
B is a dangerous trap. Because p.amount is nullable, it could be NULL either because no payment exists or because a payment exists but has no recorded amount. These two situations are not equivalent, so filtering on p.amount IS NULL would incorrectly include accounts that do have payments — just payments with a NULL amount.
C gets the logic backwards. In a LEFT JOIN, the left table's columns are always preserved; it's the right table's columns that become NULL on unmatched rows. a.account_id will never be NULL here.
D confuses the anti-join pattern entirely. Checking p.payment_id <> 0 filters for existing payments, which is the opposite of what you want — and it also excludes NULL rows, defeating the anti-join.
Your study tip: always identify the right table's primary key (guaranteed non-NULL) as your anti-join filter column. Never use a nullable column for this test.A report must list every product and any reviews written in 2026. Product P1 has only a 2025 review, P2 has a 2026 review, and P3 has no reviews. A developer proposes filtering with WHERE r.review_year = 2026 OR r.review_year IS NULL.
Which query correctly returns P1, P2, and P3 while showing only 2026 reviews?
r.review_year = 2026 directly in the ON clause means the database only attempts to match reviews from 2026. P2 gets its 2026 review matched, while P1 and P3 — having no qualifying review — are preserved with NULL review columns. All three products appear, and no 2025 data leaks through.
Option B is the developer's flawed proposal. It moves the filter to the WHERE clause, which seems clever with the IS NULL safety net. However, P1's 2025 review does join successfully, producing a non-NULL review_year of 2025 — so the IS NULL check fails, and P1 gets dropped entirely. The trap is thinking IS NULL catches all unmatched products when it actually only catches products with no reviews at all.
Option C uses an INNER JOIN, which immediately eliminates P1 (no 2026 review) and P3 (no reviews), returning only P2.
Option D also filters in WHERE and tries to exclude 2025 rows with <> 2025, but again, P1's matched 2025 row produces a non-NULL value that passes the IS NULL check inconsistently across engines.
The core study tip: ON filters before NULLs are added; WHERE filters after. If you need to restrict joined rows while keeping all left-side records, always put the condition in the ON clause.The column enrollments.enrollment_id is non-NULL. A report must show every instructor and the number of that instructor's enrollments having score >= 70. Instructors with no qualifying enrollments must show zero.
Which query satisfies both the preservation and counting requirements?
LEFT JOIN preserves all rows from the left table (instructors), but where you place your filter condition determines whether that preservation holds. Option D places AND e.score >= 70 directly in the ON clause, meaning the join itself filters which enrollment rows attach to each instructor. Instructors with no enrollments scoring ≥ 70 still appear, but their e.enrollment_id is NULL. Then COUNT(e.enrollment_id) counts only non-NULL values — returning 0 for those instructors. This satisfies both requirements perfectly.
Option A uses an INNER JOIN, which immediately drops any instructor who has no qualifying enrollments. Those instructors vanish from the result entirely, violating the preservation requirement.
Option B starts with a LEFT JOIN (good), but then adds WHERE e.score >= 70 as a post-join filter. A WHERE clause on the right table's column eliminates NULL rows — effectively converting the LEFT JOIN into an INNER JOIN and again dropping instructors with no qualifying enrollments.
Option C is close, but uses COUNT(*) instead of COUNT(e.enrollment_id). For instructors with no qualifying enrollments, the row still exists with all-NULL enrollment columns, and COUNT(*) would return 1 instead of 0 — an incorrect count.
Study tip: Remember the mantra — filter conditions that restrict right-table rows belong in the ON clause, not the WHERE clause, when you need to preserve left-table rows.A pricing report runs this query:
SELECT s.sale_id, b.band_name FROM sales s LEFT JOIN price_bands b ON s.amount BETWEEN b.minimum_amount AND b.maximum_amount;
There are three sales with amounts 50, 100, and NULL. Two bands exist: Low covers 0 through 100 inclusive, and High covers 100 through 200 inclusive.
How many rows does the query return?
The query below is run for two users:
SELECT u.user_id, o.order_id, s.shipment_id FROM users u LEFT JOIN orders o ON u.user_id = o.user_id LEFT JOIN shipments s ON o.order_id = s.order_id;
User U1 has two orders. The first order has two shipments, and the second has no shipments. User U2 has no orders.
How many result rows are returned for U1 and U2 combined?
In a multi-tenant database, invoices and customers are related by the composite key (tenant_id, customer_id). Customer IDs may be repeated in different tenants. A report must preserve every invoice, including invoices whose customer row is missing, and must never attach a customer from another tenant.
Which query correctly follows the relationship while preserving all invoices?
customer_id and tenant_id together, while keeping invoices that have no matching customer. Option C achieves this perfectly: LEFT JOIN customers c ON i.customer_id = c.customer_id AND i.tenant_id = c.tenant_id. The LEFT JOIN ensures every invoice row is preserved even when no customer matches, and both conditions in the ON clause are required simultaneously, preventing a customer from one tenant from accidentally attaching to an invoice from another.
Option A is a trap — using OR instead of AND means a row will match if either condition is true alone. This could attach a customer from a different tenant simply because the customer_id matches, violating the multi-tenant isolation requirement.
Option B looks plausible but contains a subtle, critical flaw: the tenant filter is moved into the WHERE clause instead of the ON clause. When you filter on a right-table column in WHERE after a LEFT JOIN, rows where the customer is missing produce NULL for c.tenant_id, which fails the WHERE condition — effectively converting your outer join into an inner join and dropping invoices with no customer.
Option D reverses the join direction, putting customers on the left. This preserves every customer row, not every invoice row, which is the opposite of what the report requires.
Remember: in a LEFT JOIN, conditions that restrict the right table belong in the ON clause, never in WHERE. Moving them to WHERE silently eliminates your NULL rows.A report must list every active customer, including active customers who have no shipped orders. If a customer has shipped orders, one result row should appear for each such order. Tables customers and orders are related by customer_id.
Which query meets the report requirements without retaining pending orders or removing active customers who have no shipped orders?
customers) and fills in NULLs wherever no matching row exists in the right table (orders). The critical insight is this: filter conditions on the right table belong in the ON clause, not the WHERE clause. Putting them in the WHERE clause effectively converts your LEFT JOIN into an INNER JOIN, because filtering on a right-table column after the join discards the NULL rows you were trying to keep.
Option A is correct because o.status = 'SHIPPED' appears in the ON clause. This means the join only tries to match shipped orders, but active customers with no shipped orders still appear with NULL in order_id — exactly what the report requires.
Option B moves o.status = 'SHIPPED' into the WHERE clause. Once the LEFT JOIN produces NULLs for customers without shipped orders, that WHERE condition eliminates those NULL rows, silently dropping unmatched customers.
Option C uses an INNER JOIN, which outright excludes any customer with no matching shipped orders — the opposite of the stated requirement.
Option D places c.active = 1 in the ON clause instead of the WHERE clause, then filters o.status = 'SHIPPED' in WHERE. The active filter no longer reliably excludes inactive customers, and the WHERE clause again discards NULL rows, breaking both requirements.
Study tip: On SQL exam questions involving LEFT JOINs, ask yourself: "Does this filter touch the right table?" If yes, it belongs in the ON clause — never the WHERE clause.The column employees.employee_id is a non-NULL primary key. A report must return every department and the number of employees assigned to it, showing zero for departments with no employees.
Which query produces the required employee counts?
COUNT(*) counts every row, including rows where all employee columns are NULL. So in option A, an empty department would show a count of 1 instead of 0 — because the joined row still exists, just with NULLs. That's a classic trap. Option D makes the same logical mistake but counts d.department_id instead of *; since department_id is never NULL (it's from the left table), it also returns 1 for empty departments.
Option B uses COUNT(e.employee_id). Because COUNT ignores NULL values, and e.employee_id is NULL for unmatched departments, empty departments correctly return 0. Since employee_id is a non-NULL primary key, any matched employee will always produce a non-NULL value, making this a reliable column to count.
B is the correct answer. As a study tip, always ask yourself two questions on aggregation problems: "Does my JOIN preserve the rows I need?" and "Does my COUNT column go NULL for unmatched rows?" Those two checks will catch most mistakes on questions like this.A report must list every supplier, every product belonging to that supplier, and only inspections where passed = 1. Suppliers with no products must remain, and products with no passing inspection must also remain.
Which query correctly preserves records at both required levels?
passed = 1 condition lives in the ON clause of the inspections join. This means every supplier appears (even without products), every product appears (even without passing inspections), and inspection data is optionally attached only when it qualifies.
A is wrong because the second join is an INNER JOIN on inspections, which eliminates any product that has no passing inspection — and worse, any supplier whose products all lack passing inspections disappears entirely.
B is wrong despite using two LEFT JOINs. Moving i.passed = 1 into the WHERE clause turns the outer join into an implicit inner join: rows where i.passed is NULL (products with no passing inspection) are filtered out.
D is wrong because the first join is INNER, so suppliers with no products are dropped immediately, violating the first preservation requirement.
A reliable rule: in a LEFT JOIN chain, keep optional filters in the ON clause, never in WHERE. WHERE always breaks the "optional" guarantee.