LEVEL 02

Aggregations & Grouping

GROUP BY, HAVING, aggregate functions, CASE WHEN, COALESCE, and date functions using banking data.

GROUP BY — the golden rule

Every column in SELECT must either be in GROUP BY, or wrapped in an aggregate function. Nothing else.

-- Transaction count and volume per branch
SELECT   branch,
         COUNT(*)        AS total_txns,
         SUM(amount)     AS total_amount
FROM     transactions
GROUP BY branch
ORDER BY total_amount DESC;

-- Multi-column grouping
SELECT   branch, txn_type,
         COUNT(*) AS txns, SUM(amount) AS volume
FROM     transactions
GROUP BY branch, txn_type
ORDER BY branch, txn_type;

HAVING — filter after grouping

WHERE filters rows before grouping. HAVING filters groups after aggregation.

-- Branches with volume > 1 crore AND > 500 transactions
SELECT   branch,
         COUNT(*)           AS txn_count,
         ROUND(AVG(amount), 2) AS avg_amount
FROM     transactions
WHERE    status = 'SUCCESS'       -- filters rows first
GROUP BY branch
HAVING   COUNT(*) > 500           -- filters groups after
AND      SUM(amount) > 10000000
ORDER BY txn_count DESC;

Aggregate functions

FunctionWhat it doesNULL behaviour
COUNT(*)Count all rowsIncludes NULLs
COUNT(col)Count non-null valuesSkips NULLs
COUNT(DISTINCT col)Count unique valuesSkips NULLs
SUM(col)TotalSkips NULLs
AVG(col)Mean — skips NULLsSkips NULLs ≠ SUM/COUNT(*)
MIN / MAXExtreme values — works on dates and strings tooSkips NULLs
-- Full aggregation report per branch
SELECT
    branch,
    COUNT(*)                   AS total_txns,
    COUNT(DISTINCT account_id)  AS unique_accounts,
    SUM(amount)                AS total_volume,
    ROUND(AVG(amount), 2)      AS avg_txn,
    MAX(amount)                AS largest_txn,
    MIN(txn_date)              AS first_txn_date
FROM   transactions
WHERE  status = 'SUCCESS'
GROUP BY branch
ORDER BY total_volume DESC;

CASE WHEN inside aggregates

This pattern — conditional aggregation — is the most reused pattern in reporting SQL.

-- Pivot status counts into one row per branch
SELECT
    branch,
    COUNT(*)                                        AS total,
    COUNT(CASE WHEN status = 'SUCCESS' THEN 1 END) AS success_count,
    COUNT(CASE WHEN status = 'FAILED'  THEN 1 END) AS failed_count,
    SUM(CASE WHEN status = 'SUCCESS' THEN amount ELSE 0 END) AS success_vol,
    ROUND(
        COUNT(CASE WHEN status = 'SUCCESS' THEN 1 END) * 100.0 / COUNT(*),
        2
    ) AS success_rate_pct
FROM   transactions
GROUP BY branch
ORDER BY success_rate_pct DESC;

COALESCE & NULL handling

-- COALESCE — return first non-NULL value
COALESCE(segment, 'UNCLASSIFIED')   -- works everywhere
NVL(segment, 'UNCLASSIFIED')       -- Oracle only

-- NULLIF — return NULL if two values are equal (avoids divide-by-zero)
SUM(amount) / NULLIF(COUNT(*), 0)  -- safe division

Date functions — cross-database

OperationOraclePostgreSQLMySQL
Current dateSYSDATECURRENT_DATECURDATE()
Extract yearEXTRACT(YEAR FROM col)EXTRACT(YEAR FROM col)YEAR(col)
Format dateTO_CHAR(col,'YYYY-MM')TO_CHAR(col,'YYYY-MM')DATE_FORMAT(col,'%Y-%m')
Truncate to monthTRUNC(col,'MM')DATE_TRUNC('month',col)DATE_FORMAT(col,'%Y-%m-01')
Add monthsADD_MONTHS(col,3)col + INTERVAL '3 months'DATE_ADD(col,INTERVAL 3 MONTH)
-- Monthly transaction summary — Oracle
SELECT
    TO_CHAR(txn_date, 'YYYY-MM') AS txn_month,
    branch,
    COUNT(*)                      AS txn_count,
    SUM(amount)                   AS monthly_volume
FROM   transactions
WHERE  txn_date >= ADD_MONTHS(SYSDATE, -12)
GROUP BY TO_CHAR(txn_date, 'YYYY-MM'), branch
ORDER BY txn_month, branch;

Practice Problems

Q1 · Easy
Find total amount and transaction count per txn_type. Show only types with more than 1000 transactions.
Q2 · Medium
For each branch: total transactions, success rate %, average amount for SUCCESS only, highest single transaction. Order by success rate desc.
Q3 · Medium
Find accounts with transactions in BOTH 2023 AND 2024. Show account_id and total amount for each year side-by-side using CASE WHEN inside SUM.
Q4 · Hard
Monthly report for 2024: month name, total transactions, high-value transactions (≥100000), high-value % of total, total volume.
Q5 · Hard
Branch risk report: for each branch show count of MICRO (<1000), SMALL (1K–10K), MEDIUM (10K–100K), LARGE (100K+) transactions in one row using CASE WHEN.