SQL Quiz: Basic Aggregate Functions
10 questions · exam conditions
0:00
Basic Aggregate FunctionsQuestion 1 of 10

The visits table contains exactly five rows. In row order, the customer_id values are NULL, 77, 77, NULL, and 99.

What row is returned by SELECT COUNT(*) AS all_rows, COUNT(customer_id) AS known_customers, COUNT(DISTINCT customer_id) AS unique_customers FROM visits;? The choices list values in alias order.

(5,2,2)(5, 2, 2)
(5,3,2)(5, 3, 2)
(3,3,2)(3, 3, 2)
(5,3,3)(5, 3, 3)
← Back to quizzes

SQL Quiz

SQL Quiz: Basic Aggregate Functions

Practice Basic Aggregate Functions in SQL with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.

What this quiz covers

This quiz focuses on Basic Aggregate Functions, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.

How to use this quiz

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.

All questions

Question 1

The visits table contains exactly five rows. In row order, the customer_id values are NULL, 77, 77, NULL, and 99.

What row is returned by SELECT COUNT(*) AS all_rows, COUNT(customer_id) AS known_customers, COUNT(DISTINCT customer_id) AS unique_customers FROM visits;? The choices list values in alias order.

  1. (5,2,2)(5, 2, 2)
  2. (5,3,2)(5, 3, 2) (correct answer)
  3. (3,3,2)(3, 3, 2)
  4. (5,3,3)(5, 3, 3)
Explanation: When working with COUNT in SQL, the key is remembering that each variant behaves differently with NULL values and duplicates — and this question tests all three at once. The table has five rows with customer_id values: NULL, 77, 77, NULL, 99. Let's walk through each alias. COUNT(*) counts every row regardless of content, so it returns 55. COUNT(customer_id) counts only non-NULL values in that column — the two NULL rows are excluded, leaving 77, 77, and 99, which gives 33. COUNT(DISTINCT customer_id) counts only unique non-NULL values — the two 77s collapse into one, yielding 77 and 99, so the result is 22. The returned row is (5,3,2)(5, 3, 2), confirming B is correct. Choice A (5,2,2)(5, 2, 2) mistakes COUNT(customer_id) for COUNT(DISTINCT customer_id) — it returns 22 as if duplicates were already removed, but COUNT without DISTINCT counts all non-NULL values including repeats. Choice C (3,3,2)(3, 3, 2) incorrectly applies NULL-exclusion logic to COUNT(*) as well — but COUNT(*) never skips rows, even if every column is NULL. Choice D (5,3,3)(5, 3, 3) counts 99 as a third distinct value but forgets that both 77s are the same value and only count once under DISTINCT. A useful rule of thumb: COUNT(*) = total rows, COUNT(col) = non-NULL rows, COUNT(DISTINCT col) = unique non-NULL rows. Memorize this trio and you'll handle nearly every COUNT variation on the exam.

Question 2

A shipment table contains three rows for region East, with shipped_on values 2026-01-10, NULL, and 2026-01-04. It also contains two rows for region West, both with shipped_on equal to NULL.

The query SELECT region, MIN(shipped_on), MAX(shipped_on) FROM shipment GROUP BY region ORDER BY region; is executed. Which result is correct?

  1. East has 2026-01-04 and 2026-01-10; West has NULL and NULL. (correct answer)
  2. East has 2026-01-04 and 2026-01-10; no row is returned for West.
  3. East has NULL and NULL; West also has NULL and NULL.
  4. East has 2026-01-10 and 2026-01-04; West has NULL and NULL.
Explanation: When working with aggregate functions like MIN() and MAX() in SQL, the critical concept to understand is how these functions handle NULL values within a group. SQL aggregate functions ignore NULL values during their calculations. So if a group contains the values 2026-01-10, NULL, and 2026-01-04, SQL skips the NULL and evaluates only the non-null dates. The minimum becomes 2026-01-04 and the maximum becomes 2026-01-10. This makes A the correct answer — East correctly returns 2026-01-04 and 2026-01-10 after nulls are ignored. Now, what about West, where all values are NULL? When every value in a group is NULL, there are no non-null values to aggregate, so MIN() and MAX() both return NULL. West still appears as a row because GROUP BY produces one row per distinct group regardless — it just has no non-null data to aggregate. Answer A correctly reflects this. B is wrong because it drops the West row entirely. GROUP BY never eliminates groups just because their aggregate results are null — you'd need a HAVING clause to filter groups. C is wrong because it assumes the NULL in East's data contaminates the entire group, which is not how SQL aggregation works. D is wrong because it swaps MIN and MAX for East — 2026-01-10 is the maximum, not the minimum. Remember this rule: aggregate functions silently skip NULLs, but a group itself is never dropped just for having them.

Question 3

A sensor_data table has the following readings: sensor S1 has NULL and NULL; sensor S2 has 55 and NULL; sensor S3 has 22 and 88.

Which output is produced by SELECT sensor_id, SUM(reading) FROM sensor_data GROUP BY sensor_id HAVING COUNT(reading) = COUNT(*);?

  1. Sensors S1 and S3, with totals NULL and 1010, respectively.
  2. Sensors S2 and S3, with totals 55 and 1010, respectively.
  3. All three sensors, with totals NULL, 55, and 1010, respectively.
  4. Only sensor S3, with an aggregated reading total of 1010. (correct answer)
Explanation: When you see a HAVING clause comparing two aggregate functions, your job is to figure out which groups satisfy that condition before worrying about SUM values. The key concept here is how SQL handles NULL in aggregate functions. COUNT(*) counts every row in a group, including rows with NULL values. COUNT(reading), however, counts only non-NULL values in the reading column. So the condition HAVING COUNT(reading) = COUNT(*) is essentially asking: "Which groups have zero NULLs?" — meaning every row in that group has a non-NULL reading. Let's check each sensor. S1 has two NULL readings: COUNT(reading) = 0, COUNT(*) = 2 → not equal, excluded. S2 has one non-NULL (55) and one NULL: COUNT(reading) = 1, COUNT(*) = 2 → not equal, excluded. S3 has two non-NULL readings (22 and 88): COUNT(reading) = 2, COUNT(*) = 2 → equal, included. SUM(reading) for S3 is 2+8=102 + 8 = 10. That makes D the correct answer. Answer A is wrong because S1 fails the HAVING condition and should be excluded entirely, not returned with a NULL sum. Answer B is wrong because S2 also fails — having even one NULL reading breaks the equality. Answer C is wrong for the same reason: the HAVING clause filters out S1 and S2, so returning all three sensors misunderstands the filter entirely. Remember this pattern: COUNT(column) = COUNT(*) is a clean SQL idiom for filtering groups that contain no NULLs in that column.

Question 4

The order_line table contains four rows. Their (quantity, unit_price) pairs are (2,10)(2,10), (NULL, 8), (3, NULL), and (1,5)(1,5).

What row is returned by SELECT COUNT(quantity * unit_price), SUM(quantity * unit_price), AVG(quantity * unit_price) FROM order_line;? The choices list values in select-list order.

  1. (4,25,6.25)(4, 25, 6.25)
  2. (2,25,6.25)(2, 25, 6.25)
  3. (2, NULL, NULL)
  4. (2,25,12.5)(2, 25, 12.5) (correct answer)
Explanation: When SQL aggregate functions encounter NULL values, they skip them entirely — this is the core concept being tested here. Before any aggregation happens, SQL must evaluate quantity * unit_price for each row. Any arithmetic involving NULL produces NULL, so the four computed values are 2020, NULL, NULL, and 55. Now the aggregates operate on this derived set (20,NULL,NULL,5)(20, \text{NULL}, \text{NULL}, 5). COUNT, SUM, and AVG all ignore NULLs, leaving only {20,5}\{20, 5\} as the effective input. COUNT returns 22 (two non-NULL products), SUM returns 2525, and AVG returns 25÷2=12.525 \div 2 = 12.5. That gives you (2,25,12.5)(2, 25, 12.5), confirming D is correct. Choice A is wrong on two counts: it reports COUNT = 4, as if NULLs were counted, and calculates AVG by dividing 25 by 4 rather than by the number of non-NULL values. Choice B gets COUNT and SUM right but makes the same averaging mistake — dividing 2525 by 44 instead of 22. Choice C wrongly assumes that because some inputs are NULL, the entire SUM and AVG collapse to NULL; that would only happen if all inputs were NULL. A handy rule to memorize: NULL propagates through arithmetic but is ignored by aggregates. When you see COUNT, SUM, or AVG applied to an expression that might produce NULLs, always identify how many non-NULL results that expression yields — that's the denominator AVG (and COUNT) will actually use.

Question 5

The invoice table has four amount values: 4040, 4040, NULL, and 6060.

What row is returned by SELECT SUM(amount), SUM(DISTINCT amount), COUNT(DISTINCT amount) FROM invoice;? The choices list values in select-list order.

  1. (140,140,2)(140, 140, 2)
  2. (140,100,2)(140, 100, 2) (correct answer)
  3. (100,100,2)(100, 100, 2)
  4. (140,100,3)(140, 100, 3)
Explanation: When working with aggregate functions in SQL, you need to track three behaviors carefully: how SUM handles duplicates, how DISTINCT filters them, and how NULL values are treated across all aggregates. Start with the raw data: 40,40,NULL,6040, 40, \text{NULL}, 60. SUM(amount) adds all non-NULL values, so 40+40+60=14040 + 40 + 60 = 140. NULL is silently ignored — this is standard SQL behavior. SUM(DISTINCT amount) first eliminates duplicate values, leaving the set {40,60}\{40, 60\}, then sums: 40+60=10040 + 60 = 100. Finally, COUNT(DISTINCT amount) counts unique non-NULL values. The distinct non-NULL values are 4040 and 6060, giving a count of 22. That produces (140,100,2)(140, 100, 2), which is B. Choice A (140,140,2)(140, 140, 2) treats SUM(DISTINCT amount) as if DISTINCT has no effect — it adds both 40s, ignoring that they're duplicates. Choice C (100,100,2)(100, 100, 2) applies DISTINCT logic to the plain SUM as well, incorrectly deduplicating the first column. Choice D (140,100,3)(140, 100, 3) gets the first two values right but counts NULL as a distinct value in COUNT(DISTINCT amount)NULL is never counted, regardless of DISTINCT. A reliable rule to memorize: NULL is invisible to all aggregate functions — it's excluded from SUM, AVG, and COUNT alike. Separately, DISTINCT removes duplicate non-NULL values before aggregating. Keep these two rules distinct in your mind, and questions like this become straightforward.

Question 6

The orders table contains order 11 with a total of 5050 and order 22 with a total of 8080. The payments table contains two payment rows for order 11 and one payment row for order 22.

What row is returned by SELECT COUNT(*), COUNT(DISTINCT o.order_id), SUM(o.total) FROM orders AS o JOIN payments AS p ON p.order_id = o.order_id;? The choices list values in select-list order.

  1. (3,2,130)(3, 2, 130)
  2. (2,2,130)(2, 2, 130)
  3. (3,2,180)(3, 2, 180) (correct answer)
  4. (3,3,180)(3, 3, 180)
Explanation: When a JOIN produces more rows than the original table, aggregate functions operate on those expanded rows — not the original table. That's the key concept here. After the JOIN, here's what the result set looks like: order 11 (total = 5050) appears twice (once per payment row), and order 22 (total = 8080) appears once. That gives you 3 joined rows total. Now apply each aggregate to those 3 rows:
  • COUNT(*)\text{COUNT(*)} counts all rows → 33
  • \text{COUNT(DISTINCT o.order_id)} counts unique order IDs (11 and 22) → 22
  • SUM(o.total)\text{SUM(o.total)} adds the total column across all 3 rows: 50+50+80=18050 + 50 + 80 = 180
So the correct answer is C (3,2,180)(3, 2, 180). A (3,2,130)(3, 2, 130) gets COUNT(*)\text{COUNT(*)} and COUNT(DISTINCT)\text{COUNT(DISTINCT)} right but calculates SUM\text{SUM} as if each order appeared only once (50+80=13050 + 80 = 130), ignoring that order 11 is duplicated in the join. B (2,2,130)(2, 2, 130) makes two mistakes: it counts only the distinct orders for COUNT(*)\text{COUNT(*)} instead of total rows, and it also under-calculates the SUM. D (3,3,180)(3, 3, 180) gets the SUM right but miscounts \text{COUNT(DISTINCT o.order_id)} as 33, confusing row count with distinct order count. Study tip: Whenever you join a one-to-many relationship, mentally sketch the expanded result set first. Aggregate functions always operate on that expanded set, which is why SUM\text{SUM} can produce seemingly inflated values unless you account for duplicated rows.

Question 7

Department A has two employees with salaries of 3030 and 5050. Department B has one employee with a salary of 100100.

What does SELECT AVG(dept_avg) FROM (SELECT department_id, AVG(salary) AS dept_avg FROM employee GROUP BY department_id) AS d; return?

  1. It returns 7070 because the two department averages receive equal weight. (correct answer)
  2. It returns 6060 because all three employee salaries receive equal weight.
  3. It returns 100100 because the largest department average determines the result.
  4. It returns 4040 because the department with two employees determines the result.
Explanation: When you see a subquery in the FROM clause, ask yourself: what rows does the outer query actually operate on? Here, the inner query produces one row per department, not one row per employee — and that distinction is everything. The inner query SELECT department_id, AVG(salary) FROM employee GROUP BY department_id returns exactly two rows: Department A with an average of 30+502=40\frac{30+50}{2} = 40, and Department B with an average of 100100. The outer AVG(dept_avg) then averages those two rows, giving 40+1002=70\frac{40+100}{2} = 70. So A is correct — each department contributes exactly one value to the outer average, regardless of how many employees it has. B is wrong because it describes a flat average across all three salaries: 30+50+1003=60\frac{30+50+100}{3} = 60. That would be the result if you simply wrote AVG(salary) without any grouping — the subquery structure specifically prevents this by collapsing each department first. C is wrong because SQL's AVG function doesn't return the maximum value; no such behavior exists. D is wrong because Department A's size doesn't give it extra influence in the outer query — once collapsed to a single average of 4040, it counts just once, the same as Department B. A useful rule of thumb: the outer query sees rows, not raw data. Whatever the subquery outputs becomes the dataset for the outer query. Count the rows the subquery produces, and you'll always know what the outer aggregation is working with.

Question 8

A work_log table has four rows whose hours_worked values are 88, NULL, 66, and 1010.

What row is returned by SELECT AVG(hours_worked), SUM(hours_worked) / COUNT(*) FROM work_log;? The choices list the values in select-list order.

  1. (8,8)(8, 8)
  2. (8,6)(8, 6) (correct answer)
  3. (6,8)(6, 8)
  4. (6,6)(6, 6)
Explanation: Whenever you see a question mixing AVG() with a manual SUM()/COUNT(*) calculation, the key concept being tested is how SQL handles NULL values differently across aggregate functions. Here's the critical rule: AVG(), SUM(), and COUNT(column_name) all ignore NULLs, but COUNT(*) counts every row, including those with NULL values. With values 88, NULL, 66, and 1010:
  • AVG(hours_worked) ignores the NULL and computes 8+6+103=8\frac{8 + 6 + 10}{3} = 8
  • SUM(hours_worked) also ignores the NULL: 8+6+10=248 + 6 + 10 = 24
  • COUNT(*) counts all four rows, including the NULL row: 44
  • So SUM(hours_worked) / COUNT(*) = 24/4=624 / 4 = 6
The result is (8,6)(8, 6), confirming answer B. Choice A (8,8)(8, 8) incorrectly assumes COUNT(*) returns 3 (as if it ignored the NULL), making both expressions equal. Choice C (6,8)(6, 8) swaps the two values entirely, misunderstanding which expression produces which result. Choice D (6,6)(6, 6) assumes both expressions skip the NULL row, meaning both would compute 24/4=624/4=6 — but AVG() correctly excludes the NULL from its denominator, yielding 8, not 6. The study tip to remember: COUNT(*) is the odd one out — it never ignores NULLs. Any other aggregate with a column argument (COUNT(col), SUM(col), AVG(col)) silently skips NULLs. This distinction is a favorite exam trap.

Question 9

An invoice table contains four rows whose status values are paid, pending, paid, and NULL.

What row is returned by SELECT COUNT(CASE WHEN status = 'paid' THEN 1 ELSE 0 END), SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) FROM invoice;? The choices list values in select-list order.

  1. (2,2)(2, 2)
  2. (3,2)(3, 2)
  3. (4,4)(4, 4)
  4. (4,2)(4, 2) (correct answer)
Explanation: When working with aggregate functions and CASE expressions, you need to carefully separate what COUNT does from what SUM does — they behave very differently. Here's the key insight: COUNT counts rows, while SUM adds values. The CASE WHEN status = 'paid' THEN 1 ELSE 0 END expression never returns NULL — it always returns either 1 or 0. Because COUNT ignores NULL values but this expression produces no NULLs, COUNT will count every single row in the table, including the row where status IS NULL (which hits the ELSE 0 branch). With four rows total, COUNT(...) returns 4. For SUM, the expression produces 1 for each 'paid' row and 0 for everything else. The two 'paid' rows contribute 1 + 1 = 2, the 'pending' row contributes 0, and the NULL status row also contributes 0. So SUM(...) returns 2. The result is (4,2)(4, 2), making D correct. A (2,2)(2, 2) is wrong because it treats COUNT as if it only counts rows matching 'paid' — that's what COUNT(CASE WHEN status = 'paid' THEN 1 END) would do (returning NULL instead of 0 for non-matches). B (3,2)(3, 2) wrongly excludes the NULL-status row from COUNT, forgetting that the ELSE 0 clause handles it. C (4,4)(4, 4) confuses SUM with COUNT, imagining every row contributes 1. The study tip: whenever a CASE expression includes an ELSE clause that returns a non-NULL value, COUNT will always equal the total number of rows — nothing is filtered out.

Question 10

The product table contains no rows for which category = 'retired'.

What row is returned by SELECT COUNT(*), COUNT(price), SUM(price), AVG(price) FROM product WHERE category = 'retired';? The choices list values in select-list order.

  1. (0,0,0,0)(0, 0, 0, 0)
  2. (0, 0, 0, NULL)
  3. (0, 0, NULL, NULL) (correct answer)
  4. (NULL, NULL, NULL, NULL)
Explanation: When aggregate functions encounter no matching rows, they don't all behave the same way — and that's exactly what this question tests. COUNT(*) counts rows regardless of content, so with zero matching rows it returns 0. COUNT(price) counts non-NULL values of price, which is also 0 when there are no rows. So far, so good — both COUNTs return 0. Here's where it gets interesting: SUM and AVG operate on a set of values. When that set is completely empty (no rows qualify), there are no values to sum or average. SQL returns NULL for both — not 0 — because 0 would be a misleading answer. Summing nothing is undefined, not zero. This makes C correct: (0, 0, NULL, NULL). Choice A is wrong because it assumes SUM and AVG return 0 on empty sets — a common but incorrect intuition. Choice B is wrong for the same reason regarding AVG, but also gets SUM right as NULL while incorrectly leaving AVG as 0. Choice D is wrong because it assumes COUNT(*) returns NULL on empty sets — but COUNT is specifically designed to always return an integer, never NULL. Study tip: Memorize this rule: COUNT always returns a number (never NULL), while SUM, AVG, MIN, and MAX return NULL when applied to an empty set. This distinction appears frequently on SQL exams and in real debugging scenarios.