Basic Aggregate Functions - SQL
- What does
SELECT AVG(dept_avg) FROM (SELECT department_id, AVG(salary) AS dept_avg FROM employee GROUP BY department_id) AS d;return?Answer: It returns because the two department averages receive equal weight. When you see a subquery in theFROMclause, 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 querySELECT department_id, AVG(salary) FROM employee GROUP BY department_idreturns exactly two rows: Department A with an average of , and Department B with an average of . The outerAVG(dept_avg)then averages those two rows, giving . 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: . That would be the result if you simply wroteAVG(salary)without any grouping — the subquery structure specifically prevents this by collapsing each department first. C is wrong because SQL'sAVGfunction 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 , 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. - Which output is produced by
SELECT sensor_id, SUM(reading) FROM sensor_data GROUP BY sensor_id HAVING COUNT(reading) = COUNT(*);?Answer: Only sensorS3, with an aggregated reading total of . When you see aHAVINGclause comparing two aggregate functions, your job is to figure out which groups satisfy that condition before worrying aboutSUMvalues. The key concept here is how SQL handlesNULLin aggregate functions.COUNT(*)counts every row in a group, including rows withNULLvalues.COUNT(reading), however, counts only non-NULL values in thereadingcolumn. So the conditionHAVING 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.S1has twoNULLreadings:COUNT(reading) = 0,COUNT(*) = 2→ not equal, excluded.S2has one non-NULL () and oneNULL:COUNT(reading) = 1,COUNT(*) = 2→ not equal, excluded.S3has two non-NULL readings ( and ):COUNT(reading) = 2,COUNT(*) = 2→ equal, included.SUM(reading)forS3is . That makes D the correct answer. Answer A is wrong becauseS1fails theHAVINGcondition and should be excluded entirely, not returned with aNULLsum. Answer B is wrong becauseS2also fails — having even oneNULLreading breaks the equality. Answer C is wrong for the same reason: theHAVINGclause filters outS1andS2, 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. - What row is returned by
SELECT SUM(amount), SUM(DISTINCT amount), COUNT(DISTINCT amount) FROM invoice;? The choices list values in select-list order.Answer: When working with aggregate functions in SQL, you need to track three behaviors carefully: howSUMhandles duplicates, howDISTINCTfilters them, and howNULLvalues are treated across all aggregates. Start with the raw data: .SUM(amount)adds all non-NULL values, so .NULLis silently ignored — this is standard SQL behavior.SUM(DISTINCT amount)first eliminates duplicate values, leaving the set , then sums: . Finally,COUNT(DISTINCT amount)counts unique non-NULL values. The distinct non-NULL values are and , giving a count of . That produces , which is B. Choice A treatsSUM(DISTINCT amount)as ifDISTINCThas no effect — it adds both 40s, ignoring that they're duplicates. Choice C appliesDISTINCTlogic to the plainSUMas well, incorrectly deduplicating the first column. Choice D gets the first two values right but countsNULLas a distinct value inCOUNT(DISTINCT amount)—NULLis never counted, regardless ofDISTINCT. A reliable rule to memorize: **NULLis invisible to all aggregate functions** — it's excluded fromSUM,AVG, andCOUNTalike. Separately,DISTINCTremoves duplicate non-NULL values before aggregating. Keep these two rules distinct in your mind, and questions like this become straightforward. - 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.Answer: When working with aggregate functions andCASEexpressions, you need to carefully separate whatCOUNTdoes from whatSUMdoes — they behave very differently. Here's the key insight:COUNTcounts rows, whileSUMadds values. TheCASE WHEN status = 'paid' THEN 1 ELSE 0 ENDexpression never returnsNULL— it always returns either1or0. BecauseCOUNTignoresNULLvalues but this expression produces noNULLs,COUNTwill count every single row in the table, including the row wherestatus IS NULL(which hits theELSE 0branch). With four rows total,COUNT(...)returns 4. ForSUM, the expression produces1for each'paid'row and0for everything else. The two'paid'rows contribute1 + 1 = 2, the'pending'row contributes0, and theNULLstatus row also contributes0. SoSUM(...)returns 2. The result is , making D correct. A is wrong because it treatsCOUNTas if it only counts rows matching'paid'— that's whatCOUNT(CASE WHEN status = 'paid' THEN 1 END)would do (returningNULLinstead of0for non-matches). B wrongly excludes theNULL-status row fromCOUNT, forgetting that theELSE 0clause handles it. C confusesSUMwithCOUNT, imagining every row contributes1. The study tip: whenever aCASEexpression includes anELSEclause that returns a non-NULLvalue,COUNTwill always equal the total number of rows — nothing is filtered out. - 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.Answer: Whenever you see a question mixingAVG()with a manualSUM()/COUNT(*)calculation, the key concept being tested is how SQL handles NULL values differently across aggregate functions. Here's the critical rule:AVG(),SUM(), andCOUNT(column_name)all ignore NULLs, butCOUNT(*)counts every row, including those with NULL values. With values , NULL, , and : -AVG(hours_worked)ignores the NULL and computes -SUM(hours_worked)also ignores the NULL: -COUNT(*)counts all four rows, including the NULL row: - SoSUM(hours_worked) / COUNT(*)= The result is , confirming answer B. Choice A incorrectly assumesCOUNT(*)returns 3 (as if it ignored the NULL), making both expressions equal. Choice C swaps the two values entirely, misunderstanding which expression produces which result. Choice D assumes both expressions skip the NULL row, meaning both would compute — butAVG()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. - 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.Answer: When working withCOUNTin SQL, the key is remembering that each variant behaves differently withNULLvalues and duplicates — and this question tests all three at once. The table has five rows withcustomer_idvalues:NULL, , ,NULL, . Let's walk through each alias.COUNT(*)counts every row regardless of content, so it returns .COUNT(customer_id)counts only non-NULL values in that column — the twoNULLrows are excluded, leaving , , and , which gives .COUNT(DISTINCT customer_id)counts only unique non-NULL values — the two s collapse into one, yielding and , so the result is . The returned row is , confirming B is correct. Choice A mistakesCOUNT(customer_id)forCOUNT(DISTINCT customer_id)— it returns as if duplicates were already removed, butCOUNTwithoutDISTINCTcounts all non-NULL values including repeats. Choice C incorrectly applies NULL-exclusion logic toCOUNT(*)as well — butCOUNT(*)never skips rows, even if every column isNULL. Choice D counts as a third distinct value but forgets that both s are the same value and only count once underDISTINCT. 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 everyCOUNTvariation on the exam. - 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.Answer:(0, 0, NULL, NULL)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 returns0.COUNT(price)counts non-NULL values ofprice, which is also0when there are no rows. So far, so good — both COUNTs return0. Here's where it gets interesting:SUMandAVGoperate on a set of values. When that set is completely empty (no rows qualify), there are no values to sum or average. SQL returnsNULLfor both — not0— because0would be a misleading answer. Summing nothing is undefined, not zero. This makes C correct:(0, 0, NULL, NULL). Choice A is wrong because it assumesSUMandAVGreturn0on empty sets — a common but incorrect intuition. Choice B is wrong for the same reason regardingAVG, but also getsSUMright asNULLwhile incorrectly leavingAVGas0. Choice D is wrong because it assumesCOUNT(*)returnsNULLon empty sets — butCOUNTis specifically designed to always return an integer, neverNULL. Study tip: Memorize this rule:COUNTalways returns a number (neverNULL), whileSUM,AVG,MIN, andMAXreturnNULLwhen applied to an empty set. This distinction appears frequently on SQL exams and in real debugging scenarios. - 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.Answer: 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 evaluatequantity * unit_pricefor each row. Any arithmetic involving NULL produces NULL, so the four computed values are , NULL, NULL, and . Now the aggregates operate on this derived set .COUNT,SUM, andAVGall ignore NULLs, leaving only as the effective input.COUNTreturns (two non-NULL products),SUMreturns , andAVGreturns . That gives you , confirming D is correct. Choice A is wrong on two counts: it reportsCOUNT = 4, as if NULLs were counted, and calculatesAVGby dividing 25 by 4 rather than by the number of non-NULL values. Choice B getsCOUNTandSUMright but makes the same averaging mistake — dividing by instead of . Choice C wrongly assumes that because some inputs are NULL, the entireSUMandAVGcollapse 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 seeCOUNT,SUM, orAVGapplied to an expression that might produce NULLs, always identify how many non-NULL results that expression yields — that's the denominatorAVG(andCOUNT) will actually use. - The query
SELECT region, MIN(shipped_on), MAX(shipped_on) FROM shipment GROUP BY region ORDER BY region;is executed. Which result is correct?Answer:Easthas2026-01-04and2026-01-10;WesthasNULLandNULL. When working with aggregate functions likeMIN()andMAX()in SQL, the critical concept to understand is how these functions handleNULLvalues within a group. SQL aggregate functions ignore NULL values during their calculations. So if a group contains the values2026-01-10,NULL, and2026-01-04, SQL skips theNULLand evaluates only the non-null dates. The minimum becomes2026-01-04and the maximum becomes2026-01-10. This makes A the correct answer — East correctly returns2026-01-04and2026-01-10after nulls are ignored. Now, what about West, where all values areNULL? When every value in a group isNULL, there are no non-null values to aggregate, soMIN()andMAX()both returnNULL. West still appears as a row becauseGROUP BYproduces 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 BYnever eliminates groups just because their aggregate results are null — you'd need aHAVINGclause to filter groups. C is wrong because it assumes theNULLin East's data contaminates the entire group, which is not how SQL aggregation works. D is wrong because it swapsMINandMAXfor East —2026-01-10is the maximum, not the minimum. Remember this rule: aggregate functions silently skipNULLs, but a group itself is never dropped just for having them. - 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.Answer: 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 (total = ) appears twice (once per payment row), and order (total = ) appears once. That gives you 3 joined rows total. Now apply each aggregate to those 3 rows: - counts all rows → - counts unique order IDs ( and ) → - adds the total column across all 3 rows: So the correct answer is C . A gets and right but calculates as if each order appeared only once (), ignoring that order is duplicated in the join. B makes two mistakes: it counts only the distinct orders for instead of total rows, and it also under-calculates the SUM. D gets the SUM right but miscounts as , 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 can produce seemingly inflated values unless you account for duplicated rows.