Module 2 › Session 13 · Aggregate Functions

Aggregate Functions: COUNT, SUM, AVG, MIN, MAX & STRING_AGG

Summarize 24 lakh rows into single-row executive metrics with NULL-safe division and STRING_AGG roster arrays.

Jo class mein bola, yahan nukte mein likha hai.
What I said in class, written here as key points.

← All NukteSELECT * FROM TheShiraverse
MODULE 2 · SESSION 13

Aggregate Functions:
COUNT, SUM, AVG, MIN, MAX

Summarize 24 lakh rows into single-row executive metrics using PostgreSQL 18 aggregate functions, NULL-safe division, and STRING_AGG roster arrays.

25
Scenes
1
Practice Arena
4
Practice Drills
5
Interview Questions

Today’s Agenda — Aggregate Functions

Four pillars: count mechanics, mathematical accumulators, extrema detection, and list aggregation

Pillar 01
🔢 Row Count Mechanics
01 Count all rows: COUNT(*)
02 Count non-NULL values: COUNT(column)
03 Count unique values: COUNT(DISTINCT column)
Pillar 02
💵 Mathematical Accumulators
04 Total sum: SUM(column) (ignores NULL)
05 Arithmetic mean: AVG(column)
06 Precision rounding: ROUND(AVG(...), 2)
Pillar 03
📍 Extrema & Boundaries
07 Numeric extremes: MIN(col) & MAX(col)
08 Temporal boundaries: earliest and latest dates
09 Alphabetical boundaries: first and last text
Pillar 04
🧵 String Aggregation
10 List concatenation: STRING_AGG(col, delimiter)
11 Ordering inside aggregates: ORDER BY clause
12 Unique strings: STRING_AGG(DISTINCT col, ...)
💡
Why this matters: RetailMart has 1,50,000 orders totaling over ₹100 crore in sales. Executives do not read individual order rows; they need high-level KPIs to make business decisions. SQL aggregates collapse lakhs of rows into single metric values directly inside the database in milliseconds.

Where We Left Off: Scalar Logic to Table Summaries

Bridging individual row operations and multi-row mathematical summaries

❓ SESSION 12 • COMPLETED

NULL Handling & 3VL

-- Session 12: NULL handling & fallback strategies
SELECT view_id,
COALESCE(customer_id::TEXT, 'Guest') AS visitor
FROM web_events.page_views
ORDER BY view_id LIMIT 3;
-- 1,50,316 guest visits safely handled
Session 12 mastered missing values, COALESCE fallbacks, and three-valued logic. But scalar functions operate row-by-row. How do we summarize across thousands of rows at once?
✓ Scalar NULL rules locked; table aggregation awaits
🔢 SESSION 13 • THE MISSION

Table Summary Aggregation

-- “Summarize 1,50,000 orders into core company KPIs”
SELECT
COUNT(*) AS total_orders,
ROUND(SUM(net_total), 2) AS total_revenue,
ROUND(AVG(net_total), 2) AS avg_ticket
FROM sales.orders;
SQL aggregate functions collapse entire tables into single summary rows. We learn how COUNT, SUM, AVG, MIN, MAX and STRING_AGG handle NULLs, unique values, and mathematical precision.
🔢 Today: COUNT, SUM, AVG, MIN, MAX, STRING_AGG
💡
The Core Rule: Scalar functions (UPPER, ROUND, EXTRACT) transform one row into one row. Aggregate functions (COUNT, SUM, AVG) collapse many rows into a single calculated summary.
📊
The Managing Director's Request BOARD MEETING DEADLINE • 1,50,000 ORDERS SUMMARIZED
EXECUTIVE ANALYTICS • HIGH STAKES EXAMPLE STORY • NAMES & NUMBERS MADE UP ⚠ 10 MINUTES TO BOARDROOM
STEP 01 • THE CRISIS
managing-director 02:50 PM
“Board meeting starts at 3:00 PM! I need total revenue, our average order basket, cheapest product, and top order value immediately!”
1,50,000 orders across 3 years needed summarizing in under 10 minutes.
STEP 02 • BOTTLENECKS
⚠ Board Meeting in 10 Minutes
Urgent Executive Request
The Managing Director entered the bullpen demanding total company revenue, average order value, cheapest item, and peak basket for the board.
📊 Spreadsheet Meltdown
1,50,000 Rows Crash Excel
Attempting manual export to CSV froze laptops. Calculating formulas across 1,50,000 orders led to memory crashes and copy-paste formula errors.
⚡ 14ms In-Engine Summary
Instant Multi-Metric KPI
A single SQL query with COUNT, SUM, AVG, MIN, and MAX processed all 1,50,000 orders in 14 milliseconds with zero spreadsheet exports.
STEP 03 • THE SQL FIX
IN-ENGINE AGGREGATION
🐙 Single Scan Summary
SELECT
  COUNT(*) AS total_orders,
  ROUND(SUM(net_total), 2) AS total_revenue,
  ROUND(AVG(net_total), 2) AS avg_ticket,
  MIN(net_total) AS min_order,
  MAX(net_total) AS max_order
FROM sales.orders;
-- 1,50,000 orders computed in 14ms
✓ 14ms Query Execution Delivered in 2 Minutes
STEP 04 • CORE PRINCIPLE
The Moral: Never export lakhs of rows to calculate spreadsheet summaries. Compute mathematical totals, averages, and extrema directly inside PostgreSQL with aggregate functions.

Concept Bridge: From Row Iteration to Funnel Reductions

Understanding the structural transformation when SQL summarizes data.

Pillar 01
Scalar Function Stream
Transforms each row independently. If 1,50,000 rows enter, 1,50,000 rows exit. Examples: UPPER(city), ROUND(price, 2), order_date + INTERVAL '7 days'.
Pillar 02
Table-Wide Funnel Aggregation
Funnels the entire dataset into a single summary tuple. If 1,50,000 rows enter, exactly 1 row exits. Examples: COUNT(*), SUM(net_total), AVG(salary).
Pillar 03
Pre-Filtered Summaries
Add a WHERE clause to aggregate a specific slice of data. WHERE order_status = 'Delivered' filters rows first, then computes aggregates only over that subset!
retailmart • Table-Wide Aggregation Baseline
SELECT
  COUNT(*) AS total_orders,
  ROUND(SUM(net_total), 2) AS total_revenue,
  ROUND(AVG(net_total), 2) AS avg_order_ticket
FROM sales.orders
LIMIT 1;

Visual Studio: The Aggregate Engine Funnel

Click functions below to watch 1,50,000 order records collapse into single-cell executive metrics.

TEST AGGREGATE FUNCTION:
INPUT: 1,50,000 ROWS IN sales.orders
▼ ▼ ▼
1,50,000
COUNT(*): Counts every single row in the table, regardless of NULL columns.

The Big Five: Core Aggregation Primitives

The foundation of SQL analytical metrology in PostgreSQL 18.

Pillar 01
COUNT(*) & COUNT(col)
COUNT(*) tallies all rows. COUNT(col) tallies rows where that column is NOT NULL. COUNT(DISTINCT col) tallies unique non-null values.
Pillar 02
SUM & AVG Arithmetic
SUM(num) accumulates totals. AVG(num) calculates arithmetic mean = SUM(col) / COUNT(col). Both skip NULL rows automatically!
Pillar 03
MIN & MAX Extremes
Finds boundary extremes. Works across numbers (pricing), dates (first/last order), and strings (alphabetical collation). Skips NULLs.
retailmart • Multi-Metric Financial Summary
SELECT
  COUNT(*) AS order_volume,
  ROUND(SUM(net_total), 2) AS gross_sales,
  ROUND(AVG(net_total), 2) AS avg_ticket,
  MIN(net_total) AS smallest_order,
  MAX(net_total) AS largest_order
FROM sales.orders
LIMIT 1;

Visual Studio: The Three Flavors of COUNT

Click options below to observe how NULL skipping and deduplication change counting behavior.

COUNT EVALUATION MODE:
COUNT(*) • ALL ROW TUPLES
1,04,987
COUNT(*) counts every single parcel row in sales.shipments, including pending shipments.
Flavor 01
COUNT(*)
Counts physical rows in the dataset. Never checks column contents; ignores whether columns contain NULL.
Flavor 02
COUNT(column)
Inspects the specified column. Only counts rows where that specific column is NOT NULL.
Flavor 03
COUNT(DISTINCT col)
Ignores NULLs and deduplicates values. Returns the count of distinct, unique values present.

COUNT Variations: Auditing Data Completeness

Using COUNT math to measure NULL ratios and data quality in production.

Pillar 01
Missing Rows Formula
COUNT(*) - COUNT(col) produces the exact count of missing NULL entries in any column in a single table scan!
Pillar 02
Completeness Ratio
ROUND(100.0 * COUNT(col) / COUNT(*), 1) gives the exact data completeness percentage. Always multiply by 100.0 first!
Pillar 03
Cardinality Ratio
Comparing COUNT(DISTINCT col) against COUNT(col) reveals column cardinality and duplication levels.
retailmart • Shipment Fulfillment Completeness Audit
SELECT
  COUNT(*) AS total_shipments,
  COUNT(delivered_date) AS delivered_count,
  COUNT(*) - COUNT(delivered_date) AS pending_delivery,
  ROUND(100.0 * COUNT(delivered_date) / COUNT(*), 1) AS delivery_pct,
  COUNT(DISTINCT courier_name) AS couriers_used
FROM sales.shipments
LIMIT 1;

Visual Studio: The AVG NULL-Division Trap

Click options below to observe how NULL exclusion silently distorts business averages.

COMPENSATION CALCULATION:
SCENARIO: 5 EMPLOYEES (3 SALARIED @ 50k, 60k, 70k • 2 UNASSIGNED INTERNS @ NULL)
(50k + 60k + 70k) / 3 employees
Average: ₹60,000
⚠ Silent Skew: 2 unassigned roles (NULL) are excluded from the denominator. Average appears falsely elevated!
The Danger
Silent Denominator Shrinkage
AVG(col) evaluates to SUM(col) / COUNT(col). If 20% of rows have NULL, the denominator shrinks by 20%, falsely inflating metrics!
The Defense
COALESCE Baseline
If NULL represents zero activity or cost, wrap in AVG(COALESCE(col, 0)) so the denominator includes the full population.
Zero-Division Shield
NULLIF Defense
When manually dividing two aggregate expressions: SUM(a) / NULLIF(SUM(b), 0) protects against crashing query execution on 0 values!

SUM & AVG: Arithmetic Rules & Type Precision

Preventing integer truncation and arithmetic bugs in financial summaries.

Pillar 01
Return Data Types
SUM(integer) returns BIGINT. SUM(numeric) returns NUMERIC. AVG() always returns NUMERIC or DOUBLE PRECISION.
Pillar 02
Explicit Rounding
Always wrap ROUND(AVG(amount), 2) in business reporting. Without ROUND, averages emit distracting floats like 82162.04039182.
Pillar 03
All-NULL Behavior
If every single row in a column is NULL, both SUM() and AVG() evaluate to NULL (not 0). Use COALESCE if 0 is required.
retailmart • Order Financial Totals
SELECT
  COUNT(*)
    AS total_orders,
  ROUND(
    SUM(net_total), 2
  ) AS total_net_revenue,
  ROUND(
    AVG(net_total), 2
  ) AS avg_order_value
FROM sales.orders
LIMIT 1;

Visual Studio: MIN & MAX Range Analysis

Click data dimensions below to explore boundary evaluation across numbers, dates, and strings.

TEST DATA TYPE SPECTRUM:
NUMERIC PRICING SPECTRUM
₹26.37 → ₹1,49,578.93
MIN(price) & MAX(price) evaluate numeric values. Span = ₹1,49,552.56 catalog price spread.
Numeric Boundary
Price Range & Spread
Compute catalog price spread directly with arithmetic: MAX(price) - MIN(price).
Temporal Boundary
Timeline Horizons
MIN(order_date)::DATE is earliest transaction; MAX(order_date)::DATE is newest transaction.
Textual Boundary
Alphabetical Collation
MIN(first_name) and MAX(first_name) evaluate database collation alphabetical extremes.

MIN & MAX: Boundary Extrapolations & Margins

Evaluating mathematical expressions and spread arithmetic inside aggregate calls.

Pillar 01
Expressions Inside Aggregates
You can evaluate full arithmetic inside aggregates: MIN(price - cost_price) computes lowest margin per unit.
Pillar 02
Arithmetic Between Aggregates
You can subtract or divide aggregate results: MAX(price) - MIN(price) or SUM(profit) / SUM(revenue).
Pillar 03
Pre-Filter with WHERE
Filter out invalid or uncosted inventory before calculating extremes: WHERE cost_price IS NOT NULL.
retailmart • Catalog Product Margin Range
SELECT
  COUNT(*) AS catalog_skus,
  MIN(price) AS cheapest_item,
  MAX(price) AS priciest_item,
  MAX(price) - MIN(price) AS price_spread,
  MIN(price - cost_price) AS min_unit_margin,
  MAX(price - cost_price) AS max_unit_margin
FROM products.products
WHERE cost_price IS NOT NULL
LIMIT 1;

Visual Studio: STRING_AGG List Concatenation

Click options below to watch vertical row items transform into clean horizontal rosters.

TEST CONCATENATION:
BASIC STRING_AGG
Cashier, Store Manager, Cashier, Store Associate, Assistant Manager
Basic STRING_AGG(role, ", ") concatenates all non-null rows, preserving duplicates.
Without STRING_AGG
Multiple Vertical Rows
Store 1 • Cashier
Store 1 • Store Manager
Store 1 • Cashier
Store 1 • Store Associate
Repetitive row dumps that overwhelm executive reporting.
With STRING_AGG
1 Clean Summary Cell
Store 1 Roles:
Assistant Manager | Cashier | Store Associate | Store Manager
Deduplicated, sorted roster string embedded seamlessly into a single row.
Modifiers
DISTINCT & ORDER BY
STRING_AGG(DISTINCT role, ' | ' ORDER BY role)
Removes duplicates and guarantees alphabetical presentation!

STRING_AGG: Text Concatenation Across Rows

PostgreSQL's built-in group concatenation aggregate function.

Pillar 01
Mandatory Delimiter
STRING_AGG(col, delimiter) strictly requires a delimiter (e.g. ', ' or ' | ') as its second argument.
Pillar 02
Internal ORDER BY
ORDER BY placed inside the function parenthesis sorts the strings before concatenation without affecting query row sort.
Pillar 03
Deduplication with DISTINCT
Applying DISTINCT inside ensures identical values within the dataset are only listed once in the output string.
retailmart • Distinct Store 1 Roles Roster
SELECT
  COUNT(*)
    AS store_headcount,
  STRING_AGG(
    DISTINCT role,
    ' | '
    ORDER BY role
  ) AS distinct_roles
FROM stores.employees
WHERE store_id = 1
LIMIT 1;

Common Mistakes: Aggregate Function Pitfalls

Three silent mathematical bugs that distort executive reports.

Mistake 01
Integer Division in Percentages
COUNT(phone) / COUNT(*) * 100 = 0
In PostgreSQL, BIGINT / BIGINT truncates toward zero (e.g. 47500 / 50000 = 0). Fix: Multiply by 100.0 or cast numerator: COUNT(phone)::NUMERIC / COUNT(*) * 100.
Mistake 02
Silent AVG(NULL) Exclusion
AVG(salary) ignores unassigned headcount
AVG excludes NULL rows from both sum and count. If NULL represents zero compensation, average is overstated. Fix: Use AVG(COALESCE(salary, 0)).
Mistake 03
Missing Subquery Parens
SELECT SELECT COUNT(*) FROM ... AS cnt
When combining multiple table counts in one SELECT, each subquery MUST be wrapped in its own parentheses: (SELECT COUNT(*) FROM ...) AS cnt.
65%
🧠Practice Time

Interview Studio: Master Aggregate Function Questions

Five core SQL aggregate metrology questions interview panels ask data analysts and engineers.

Q1 Three Flavors of COUNT
Q2 How AVG Handles NULLs
Q3 SUM vs COUNT for Totals
Q4 MIN & MAX on Non-Numbers
Q5 Whole-Table Scan Efficiency
Q1 · Three Flavors of COUNT
Explain the exact operational difference between COUNT(*), COUNT(col), and COUNT(DISTINCT col).
Answer to give:
COUNT(*) tallies every physical row tuple in the table or partition, completely ignoring whether any or all columns contain NULL. COUNT(col) inspects the specific column and increments only when that column evaluates to NOT NULL. COUNT(DISTINCT col) discards NULLs and deduplicates values, returning the unique cardinality count. In RetailMart, subtracting COUNT(*) - COUNT(col) gives the exact missing NULL count in one table pass.

Self-Check Studio: 6 Aggregate Retention Cards

Click any flashcard to flip and test your understanding of PostgreSQL aggregate mechanics.

CLICK TO FLIP • SELF-CHECK
What does COUNT(*) return on a table containing 10,000 rows where every column is NULL?
ANSWER: 10,000. COUNT(*) tallies physical row tuples and does not evaluate column contents.
CLICK TO FLIP • SELF-CHECK
What is the result of SUM(col) when every single row in the partition contains NULL?
ANSWER: NULL (not 0). If all rows are NULL, SUM() returns NULL. Use COALESCE(SUM(col), 0) to guarantee 0.
CLICK TO FLIP • SELF-CHECK
Why does COUNT(phone) / COUNT(*) * 100 evaluate to 0 in PostgreSQL?
ANSWER: Integer division. BIGINT / BIGINT truncates decimals toward zero. Multiply by 100.0 first!
CLICK TO FLIP • SELF-CHECK
How do you order strings inside STRING_AGG before concatenation occurs?
ANSWER: Write ORDER BY inside the function parenthesis: STRING_AGG(role, ', ' ORDER BY role).
CLICK TO FLIP • SELF-CHECK
How does COUNT(DISTINCT col) treat NULL values?
ANSWER: It ignores NULLs completely. If a column contains only NULL values, COUNT(DISTINCT col) returns 0.
CLICK TO FLIP • SELF-CHECK
Can you use aggregate functions like COUNT(*) inside a WHERE clause?
ANSWER: No! WHERE filters individual rows before aggregation occurs. Aggregates in WHERE throw a syntax error.

Key Takeaways: The 4 Aggregate Laws

Core rules matching today's 4 curriculum pillars for production SQL analytics.

01
The Big Five Aggregates
COUNT tallies rows, SUM accumulates numbers, AVG divides by non-null count, and MIN/MAX find extremes across numbers, dates, and text strings.
02
Completeness & NULL Math
COUNT(*) - COUNT(col) measures missing data. Guard against silent AVG denominator shrinking by using COALESCE(col, 0) when NULL means zero.
03
Range Analysis & Extremes
You can evaluate arithmetic expressions inside aggregates (MIN(price - cost)) and compute spreads between aggregates (MAX(price) - MIN(price)).
04
List Aggregation: STRING_AGG
STRING_AGG requires a delimiter string. Use internal DISTINCT to deduplicate roster items and internal ORDER BY for alphabetical sorting.

Quick Reference: Aggregate Functions Blueprint

Production SQL syntax reference for table-wide aggregation and metrology.

CORE AGGREGATE PRIMITIVES
COUNT Variations▶ Run
SELECT
  COUNT(*) AS total,
  COUNT(delivered_date) AS delivered
FROM sales.shipments LIMIT 1;
SUM & AVG Financials▶ Run
SELECT
  ROUND(SUM(net_total), 2) AS rev,
  ROUND(AVG(net_total), 2) AS avg_val
FROM sales.orders LIMIT 1;
MIN & MAX Bounds▶ Run
SELECT
  MIN(price) AS cheapest,
  MAX(price) AS priciest
FROM products.products LIMIT 1;
Timeline Extremes▶ Run
SELECT
  MIN(order_date)::DATE AS first_dt,
  MAX(order_date)::DATE AS last_dt
FROM sales.orders LIMIT 1;
DATA QUALITY & LIST AGGREGATION
Missing NULL Count▶ Run
SELECT
  COUNT(*) - COUNT(delivered_date) AS missing_cnt
FROM sales.shipments LIMIT 1;
Safe Percentage Ratio▶ Run
SELECT
  ROUND(100.0 * COUNT(delivered_date) / COUNT(*), 1) AS pct
FROM sales.shipments LIMIT 1;
Distinct Roster with STRING_AGG▶ Run
SELECT
  STRING_AGG(DISTINCT role, ' | ' ORDER BY role) AS roles
FROM stores.employees
WHERE store_id = 1 LIMIT 1;
Scalar Subquery Counts▶ Run
SELECT
  (SELECT COUNT(*) FROM customers.customers) AS custs,
  (SELECT COUNT(*) FROM sales.orders) AS orders;

Take-Home Challenge: 6 Queries on RetailMart

Apply today's aggregate functions in the playground. Every task has verified non-zero results.

01
Stores & Geography Audit
From stores.stores, count total active stores (200), distinct cities served (66), and earliest and newest opening dates.
02
Delivered Revenue Metrology
From sales.orders, for order_status = 'Delivered' (82,540 orders), compute total revenue (₹676.95 crore) and average order value.
03
Warehouse Inventory Health
From products.inventory, calculate total units on hand (1,13,76,953) and count critical items where quantity_on_hand <= reorder_level (17,475).
04
Logistics Partner Fulfillment
From sales.shipments, for carrier Delhivery (26,347 parcels), compute delivery fulfillment completion percentage (85.8%).
05
Workforce Payroll & High Earners
From stores.employees (3,000 headcount), calculate monthly payroll (₹18.98 crore) and count high earners earning ≥ ₹1,00,000 (578).
06
Department Role Roster
From stores.employees, for dept_id = 1 (300 staff), produce a pipe-separated roster of distinct roles sorted alphabetically using STRING_AGG.
✓
Ready to Execute?
Run each challenge in the interactive playground. Verify your results against real PostgreSQL data.

Your SQL Journey: Session 13 Completed

Track your progress across the complete 59-session curriculum.

MODULE 1 S01–S08: Foundations & Sorting COMPLETED ✓
MODULE 2 · S09–S12 Functions, CASE & NULLs COMPLETED ✓
MODULE 2 · S13 Aggregate Functions YOU ARE HERE ●
MODULE 2 · S14 GROUP BY & HAVING UP NEXT →

Coming Next: GROUP BY & HAVING

Partitioning datasets into categorical piles and post-aggregation filtering.

SESSION 14 PREVIEW
GROUP BY Partitioning Piles
Instead of summarizing the whole table into 1 row, split 1,50,000 orders into store or status piles and compute independent metrics per pile!
THE GOLDEN RULE
Strict Select-List Alignment
Master the fundamental rule of SQL grouping: every non-aggregated column in SELECT must be declared in GROUP BY.
POST-AGGREGATE FILTER
HAVING Clause Mastery
Discover why WHERE cannot filter aggregate sums and how HAVING acts as the post-aggregation judge to eliminate underperforming groups.
GROUP BY & HAVING on Live Schedule → Review From Beginning ↺
→ next   ← back   F fullscreen   H hide bars   N notes
1 / 41