Bird
Raised Fist0
Interview Prepdatabase-management-systemsmediumAmazonGoogleFlipkartPhonePeZepto

Aggregate Functions - GROUP BY, HAVING, Window Functions

Choose your preparation mode3 modes available
🎯
Aggregate Functions - GROUP BY, HAVING, Window Functions
mediumDBMSAmazonGoogleFlipkart

Imagine you are analyzing sales data for an e-commerce platform and need to summarize total sales per category, filter categories with high sales, and rank products within categories by revenue.

💡 This problem focuses on SQL aggregate functions and window functions, which are essential for summarizing and analyzing grouped data. Beginners often struggle because they confuse WHERE with HAVING, or don't understand how window functions differ from aggregates.
📋
Problem Statement

Given a sales dataset with columns (product_id, category, sales_amount, sale_date), write SQL queries to: 1) Calculate total sales per category using GROUP BY and aggregate functions; 2) Filter categories with total sales above a threshold using HAVING; 3) Rank products within each category by their total sales using window functions such as RANK() or DENSE_RANK(). Output the results accordingly.

Number of rows n can be up to 10^5sales_amount is a positive decimal numbercategory and product_id are non-null stringssale_date is a valid date
💡
Example
Input"Table sales:\nproduct_id | category | sales_amount | sale_date\nP1 | Electronics | 100 | 2023-01-01\nP2 | Electronics | 150 | 2023-01-02\nP3 | Books | 200 | 2023-01-01\nP4 | Books | 50 | 2023-01-03"
OutputTotal sales per category: Electronics | 250 Books | 250 Categories with sales > 200: Electronics | 250 Books | 250 Ranking products by sales within category: Category | product_id | total_sales | rank Books | P3 | 200 | 1 Books | P4 | 50 | 2 Electronics | P2 | 150 | 1 Electronics | P1 | 100 | 2

First, we sum sales_amount grouped by category. Then, we filter categories with total sales above 200 using HAVING. Finally, we rank products within each category by their total sales using RANK() window function.

  • No sales data (empty table) → returns empty result sets
  • All categories have total sales below threshold → HAVING filters out all groups
  • Multiple products with same sales amount → RANK() assigns same rank, DENSE_RANK() assigns consecutive ranks
  • Single product category → rank is always 1
⚠️
Common Mistakes
Using WHERE instead of HAVING to filter aggregated results

Query returns error or incorrect results because WHERE filters rows before aggregation

✅ Use HAVING clause to filter groups after aggregation

Not including all non-aggregated columns in GROUP BY

SQL error or unpredictable results due to ambiguous grouping

✅ Include all selected non-aggregated columns in GROUP BY clause

Confusing RANK() and DENSE_RANK() window functions

Ranks may skip numbers or not, leading to incorrect interpretation of ranking

✅ Choose RANK() if gaps in ranking are acceptable, DENSE_RANK() for consecutive ranks

Forgetting to partition by category in window functions

Ranking is done over entire dataset instead of per category, producing wrong results

✅ Use PARTITION BY category in window function to rank within groups

Not handling ties properly when ranking products

Ranks assigned incorrectly, affecting top-N queries or business logic

✅ Use appropriate window function and ordering to handle ties as per requirements

🧠
Brute Force (Manual Aggregation and Filtering)
💡 This approach manually simulates aggregation and filtering by iterating over data, which helps beginners understand the underlying mechanics of GROUP BY and HAVING before using SQL syntax.

Intuition

Group rows by category, sum sales amounts per group, then filter groups by total sales threshold. Finally, rank products within each category by sorting their total sales.

Algorithm

  1. Initialize a dictionary to accumulate total sales per category.
  2. Iterate over each sale record, add sales_amount to the corresponding category total.
  3. Filter categories whose total sales exceed the threshold.
  4. For ranking, group sales by category and product, sum sales per product, then sort and assign ranks.
💡 This step-by-step manual approach clarifies how SQL aggregates and filters data internally, which is often abstracted away in SQL queries.
</>
Code
from collections import defaultdict

def aggregate_and_rank(sales, threshold):
    # Step 1: Aggregate total sales per category
    category_totals = defaultdict(float)
    for record in sales:
        category_totals[record['category']] += record['sales_amount']

    # Step 2: Filter categories by threshold
    filtered_categories = {cat for cat, total in category_totals.items() if total > threshold}

    # Step 3: Aggregate sales per product within filtered categories
    product_totals = defaultdict(float)
    for record in sales:
        if record['category'] in filtered_categories:
            key = (record['category'], record['product_id'])
            product_totals[key] += record['sales_amount']

    # Step 4: Rank products within each category
    from operator import itemgetter
    result = []
    for category in filtered_categories:
        products = [(prod, total) for (cat, prod), total in product_totals.items() if cat == category]
        products.sort(key=lambda x: x[1], reverse=True)
        rank = 1
        prev_total = None
        for i, (prod, total) in enumerate(products):
            if total != prev_total:
                rank = i + 1
            result.append({'category': category, 'product_id': prod, 'total_sales': total, 'rank': rank})
            prev_total = total
    return category_totals, filtered_categories, result

# Driver code
sales_data = [
    {'product_id': 'P1', 'category': 'Electronics', 'sales_amount': 100, 'sale_date': '2023-01-01'},
    {'product_id': 'P2', 'category': 'Electronics', 'sales_amount': 150, 'sale_date': '2023-01-02'},
    {'product_id': 'P3', 'category': 'Books', 'sales_amount': 200, 'sale_date': '2023-01-01'},
    {'product_id': 'P4', 'category': 'Books', 'sales_amount': 50, 'sale_date': '2023-01-03'}
]
threshold = 200
category_totals, filtered_categories, ranked_products = aggregate_and_rank(sales_data, threshold)
print("Category Totals:", category_totals)
print("Filtered Categories:", filtered_categories)
print("Ranked Products:", ranked_products)
Line Notes
category_totals = defaultdict(float)Initialize dictionary to accumulate sums per category
for record in sales:Iterate over each sale record to aggregate data
filtered_categories = {cat for cat, total in category_totals.items() if total > threshold}Filter categories based on total sales threshold
products.sort(key=lambda x: x[1], reverse=True)Sort products descending by total sales for ranking
import java.util.*;

public class AggregateFunctions {
    public static void main(String[] args) {
        List<Map<String, Object>> sales = Arrays.asList(
            Map.of("product_id", "P1", "category", "Electronics", "sales_amount", 100.0, "sale_date", "2023-01-01"),
            Map.of("product_id", "P2", "category", "Electronics", "sales_amount", 150.0, "sale_date", "2023-01-02"),
            Map.of("product_id", "P3", "category", "Books", "sales_amount", 200.0, "sale_date", "2023-01-01"),
            Map.of("product_id", "P4", "category", "Books", "sales_amount", 50.0, "sale_date", "2023-01-03")
        );
        double threshold = 200.0;
        Map<String, Double> categoryTotals = new HashMap<>();
        for (Map<String, Object> record : sales) {
            String category = (String) record.get("category");
            double amount = (double) record.get("sales_amount");
            categoryTotals.put(category, categoryTotals.getOrDefault(category, 0.0) + amount);
        }
        Set<String> filteredCategories = new HashSet<>();
        for (Map.Entry<String, Double> entry : categoryTotals.entrySet()) {
            if (entry.getValue() > threshold) {
                filteredCategories.add(entry.getKey());
            }
        }
        Map<String, Double> productTotals = new HashMap<>();
        for (Map<String, Object> record : sales) {
            String category = (String) record.get("category");
            if (filteredCategories.contains(category)) {
                String product = (String) record.get("product_id");
                double amount = (double) record.get("sales_amount");
                String key = category + "|" + product;
                productTotals.put(key, productTotals.getOrDefault(key, 0.0) + amount);
            }
        }
        for (String category : filteredCategories) {
            List<Map.Entry<String, Double>> products = new ArrayList<>();
            for (Map.Entry<String, Double> entry : productTotals.entrySet()) {
                if (entry.getKey().startsWith(category + "|")) {
                    products.add(entry);
                }
            }
            products.sort((a, b) -> Double.compare(b.getValue(), a.getValue()));
            int rank = 1;
            Double prevTotal = null;
            for (int i = 0; i < products.size(); i++) {
                double total = products.get(i).getValue();
                if (prevTotal == null || total != prevTotal) {
                    rank = i + 1;
                }
                String product = products.get(i).getKey().split("\\|")[1];
                System.out.println("Category: " + category + ", Product: " + product + ", Total Sales: " + total + ", Rank: " + rank);
                prevTotal = total;
            }
        }
    }
}
Line Notes
Map<String, Double> categoryTotals = new HashMap<>();Create map to accumulate sales per category
for (Map<String, Object> record : sales)Iterate over sales records to aggregate
if (entry.getValue() > threshold)Filter categories exceeding sales threshold
products.sort((a, b) -> Double.compare(b.getValue(), a.getValue()));Sort products descending by sales for ranking
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

struct Sale {
    string product_id;
    string category;
    double sales_amount;
    string sale_date;
};

int main() {
    vector<Sale> sales = {
        {"P1", "Electronics", 100, "2023-01-01"},
        {"P2", "Electronics", 150, "2023-01-02"},
        {"P3", "Books", 200, "2023-01-01"},
        {"P4", "Books", 50, "2023-01-03"}
    };
    double threshold = 200;
    unordered_map<string, double> category_totals;
    for (auto &record : sales) {
        category_totals[record.category] += record.sales_amount;
    }
    vector<string> filtered_categories;
    for (auto &entry : category_totals) {
        if (entry.second > threshold) {
            filtered_categories.push_back(entry.first);
        }
    }
    map<pair<string, string>, double> product_totals;
    for (auto &record : sales) {
        if (find(filtered_categories.begin(), filtered_categories.end(), record.category) != filtered_categories.end()) {
            product_totals[{record.category, record.product_id}] += record.sales_amount;
        }
    }
    for (auto &category : filtered_categories) {
        vector<pair<string, double>> products;
        for (auto &entry : product_totals) {
            if (entry.first.first == category) {
                products.push_back({entry.first.second, entry.second});
            }
        }
        sort(products.begin(), products.end(), [](auto &a, auto &b) { return a.second > b.second; });
        int rank = 1;
        double prev_total = -1;
        for (int i = 0; i < (int)products.size(); i++) {
            if (products[i].second != prev_total) {
                rank = i + 1;
            }
            cout << "Category: " << category << ", Product: " << products[i].first << ", Total Sales: " << products[i].second << ", Rank: " << rank << endl;
            prev_total = products[i].second;
        }
    }
    return 0;
}
Line Notes
unordered_map<string, double> category_totals;Map to accumulate sales per category
for (auto &record : sales)Iterate over sales to sum amounts
if (entry.second > threshold)Filter categories by sales threshold
sort(products.begin(), products.end(), [](auto &a, auto &b) { return a.second > b.second; });Sort products descending for ranking
const sales = [
  { product_id: 'P1', category: 'Electronics', sales_amount: 100, sale_date: '2023-01-01' },
  { product_id: 'P2', category: 'Electronics', sales_amount: 150, sale_date: '2023-01-02' },
  { product_id: 'P3', category: 'Books', sales_amount: 200, sale_date: '2023-01-01' },
  { product_id: 'P4', category: 'Books', sales_amount: 50, sale_date: '2023-01-03' }
];
const threshold = 200;

function aggregateAndRank(sales, threshold) {
  const categoryTotals = {};
  sales.forEach(({ category, sales_amount }) => {
    categoryTotals[category] = (categoryTotals[category] || 0) + sales_amount;
  });
  const filteredCategories = Object.entries(categoryTotals)
    .filter(([_, total]) => total > threshold)
    .map(([cat]) => cat);
  const productTotals = {};
  sales.forEach(({ category, product_id, sales_amount }) => {
    if (filteredCategories.includes(category)) {
      const key = `${category}|${product_id}`;
      productTotals[key] = (productTotals[key] || 0) + sales_amount;
    }
  });
  const result = [];
  filteredCategories.forEach(category => {
    const products = Object.entries(productTotals)
      .filter(([key]) => key.startsWith(category + '|'))
      .map(([key, total]) => [key.split('|')[1], total]);
    products.sort((a, b) => b[1] - a[1]);
    let rank = 1;
    let prevTotal = null;
    products.forEach(([prod, total], i) => {
      if (total !== prevTotal) rank = i + 1;
      result.push({ category, product_id: prod, total_sales: total, rank });
      prevTotal = total;
    });
  });
  return { categoryTotals, filteredCategories, rankedProducts: result };
}

const output = aggregateAndRank(sales, threshold);
console.log('Category Totals:', output.categoryTotals);
console.log('Filtered Categories:', output.filteredCategories);
console.log('Ranked Products:', output.rankedProducts);
Line Notes
const categoryTotals = {};Object to accumulate sales sums per category
sales.forEach(({ category, sales_amount }) => {Iterate over sales to aggregate totals
const filteredCategories = Object.entries(categoryTotals).filter(...).map(...)Filter categories exceeding threshold
products.sort((a, b) => b[1] - a[1]);Sort products descending by sales for ranking
⏱
Complexity
TimeO(n + m log m) where n is number of sales and m is number of products in filtered categories
SpaceO(n) for storing aggregates and intermediate results

We iterate over all sales once for aggregation (O(n)), then sort products per category (m log m).

💡 For 100,000 sales, this means roughly 100,000 iterations plus sorting products, which is feasible but not efficient for very large datasets.
Interview Verdict: Use only to introduce concepts - not practical for large datasets

This approach is too manual and inefficient for real databases but is invaluable for understanding how GROUP BY and HAVING work internally.

🧠
SQL GROUP BY and HAVING Clauses
💡 This approach uses SQL's GROUP BY to aggregate data and HAVING to filter groups, which is the standard and efficient way to summarize and filter grouped data in databases.

Intuition

GROUP BY collects rows sharing the same category, aggregate functions compute totals per group, and HAVING filters groups based on aggregate results.

Algorithm

  1. Write a SELECT statement with category and SUM(sales_amount).
  2. Use GROUP BY category to aggregate sales per category.
  3. Add HAVING clause to filter categories with total sales above threshold.
  4. Execute query to get filtered aggregated results.
💡 This approach leverages SQL's declarative syntax to express aggregation and filtering succinctly and efficiently.
</>
Code
import sqlite3

# Setup in-memory SQLite database
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE sales (product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)''')
sales_data = [
    ('P1', 'Electronics', 100, '2023-01-01'),
    ('P2', 'Electronics', 150, '2023-01-02'),
    ('P3', 'Books', 200, '2023-01-01'),
    ('P4', 'Books', 50, '2023-01-03')
]
cursor.executemany('INSERT INTO sales VALUES (?, ?, ?, ?)', sales_data)
conn.commit()

threshold = 200
query = '''
SELECT category, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY category
HAVING total_sales > ?
'''
cursor.execute(query, (threshold,))
results = cursor.fetchall()
print('Filtered categories with total sales >', threshold)
for row in results:
    print(row)

conn.close()
Line Notes
conn = sqlite3.connect(':memory:')Create temporary in-memory database for testing
cursor.execute('''CREATE TABLE salesDefine sales table schema
cursor.executemany('INSERT INTO sales VALUES (?, ?, ?, ?)', sales_data)Insert sample sales data
cursor.execute(query, (threshold,))Execute GROUP BY and HAVING query with parameter
import java.sql.*;

public class SQLGroupByHaving {
    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
        Statement stmt = conn.createStatement();
        stmt.execute("CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)");
        String[] inserts = {
            "INSERT INTO sales VALUES('P1', 'Electronics', 100, '2023-01-01')",
            "INSERT INTO sales VALUES('P2', 'Electronics', 150, '2023-01-02')",
            "INSERT INTO sales VALUES('P3', 'Books', 200, '2023-01-01')",
            "INSERT INTO sales VALUES('P4', 'Books', 50, '2023-01-03')"
        };
        for (String sql : inserts) stmt.execute(sql);
        double threshold = 200;
        PreparedStatement ps = conn.prepareStatement(
            "SELECT category, SUM(sales_amount) AS total_sales FROM sales GROUP BY category HAVING total_sales > ?"
        );
        ps.setDouble(1, threshold);
        ResultSet rs = ps.executeQuery();
        System.out.println("Filtered categories with total sales > " + threshold);
        while (rs.next()) {
            System.out.println(rs.getString("category") + " | " + rs.getDouble("total_sales"));
        }
        rs.close();
        ps.close();
        stmt.close();
        conn.close();
    }
}
Line Notes
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")Create in-memory SQLite connection
stmt.execute("CREATE TABLE salesCreate sales table
PreparedStatement ps = conn.prepareStatement(...)Prepare parameterized GROUP BY and HAVING query
while (rs.next())Iterate over filtered aggregated results
#include <iostream>
#include <sqlite3.h>

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
    for(int i = 0; i < argc; i++) {
        std::cout << azColName[i] << ": " << (argv[i] ? argv[i] : "NULL") << " | ";
    }
    std::cout << std::endl;
    return 0;
}

int main() {
    sqlite3 *db;
    char *errMsg = 0;
    int rc = sqlite3_open(":memory:", &db);
    if(rc) {
        std::cerr << "Can't open database: " << sqlite3_errmsg(db) << std::endl;
        return 1;
    }
    const char *create_sql = "CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT);";
    rc = sqlite3_exec(db, create_sql, 0, 0, &errMsg);
    const char *insert_sql = "INSERT INTO sales VALUES('P1','Electronics',100,'2023-01-01');"
                             "INSERT INTO sales VALUES('P2','Electronics',150,'2023-01-02');"
                             "INSERT INTO sales VALUES('P3','Books',200,'2023-01-01');"
                             "INSERT INTO sales VALUES('P4','Books',50,'2023-01-03');";
    rc = sqlite3_exec(db, insert_sql, 0, 0, &errMsg);
    const char *query = "SELECT category, SUM(sales_amount) AS total_sales FROM sales GROUP BY category HAVING total_sales > 200;";
    std::cout << "Filtered categories with total sales > 200" << std::endl;
    rc = sqlite3_exec(db, query, callback, 0, &errMsg);
    sqlite3_close(db);
    return 0;
}
Line Notes
sqlite3_open(":memory:", &db)Open in-memory SQLite database
sqlite3_exec(db, create_sql, 0, 0, &errMsg)Create sales table
sqlite3_exec(db, insert_sql, 0, 0, &errMsg)Insert sample data
sqlite3_exec(db, query, callback, 0, &errMsg)Execute GROUP BY and HAVING query and print results
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');

db.serialize(() => {
  db.run("CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)");
  const stmt = db.prepare("INSERT INTO sales VALUES (?, ?, ?, ?)");
  stmt.run('P1', 'Electronics', 100, '2023-01-01');
  stmt.run('P2', 'Electronics', 150, '2023-01-02');
  stmt.run('P3', 'Books', 200, '2023-01-01');
  stmt.run('P4', 'Books', 50, '2023-01-03');
  stmt.finalize();

  const threshold = 200;
  db.all("SELECT category, SUM(sales_amount) AS total_sales FROM sales GROUP BY category HAVING total_sales > ?", [threshold], (err, rows) => {
    if (err) throw err;
    console.log(`Filtered categories with total sales > ${threshold}`);
    rows.forEach(row => {
      console.log(row.category + ' | ' + row.total_sales);
    });
  });
});

// Run with: node this_file.js
Line Notes
const db = new sqlite3.Database(':memory:');Create in-memory SQLite database
db.run("CREATE TABLE salesCreate sales table schema
stmt.run(...)Insert sample sales data
db.all("SELECT category, SUM(sales_amount) ...Execute GROUP BY and HAVING query with parameter
⏱
Complexity
TimeO(n) for aggregation plus database internal optimizations
SpaceO(k) where k is number of distinct categories

GROUP BY aggregates in a single pass; HAVING filters groups efficiently inside the database engine.

💡 This approach scales well for large datasets because databases optimize aggregation internally.
Interview Verdict: Accepted - standard and efficient approach

This is the recommended approach for aggregation and filtering in SQL interviews.

🧠
Window Functions for Ranking Products Within Categories
💡 Window functions like RANK() and DENSE_RANK() allow ranking rows within partitions without collapsing rows, which is useful for advanced analytics beyond simple aggregation.

Intuition

Partition data by category, order products by total sales descending, and assign ranks within each partition to identify top products per category.

Algorithm

  1. Write a subquery to aggregate total sales per product and category.
  2. Use a window function RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) to assign ranks.
  3. Select desired columns including rank from the subquery.
  4. Optionally filter or order results as needed.
💡 Window functions keep row-level detail while adding ranking info, enabling complex queries like top-N per group.
</>
Code
import sqlite3

conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE sales (product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)''')
sales_data = [
    ('P1', 'Electronics', 100, '2023-01-01'),
    ('P2', 'Electronics', 150, '2023-01-02'),
    ('P3', 'Books', 200, '2023-01-01'),
    ('P4', 'Books', 50, '2023-01-03')
]
cursor.executemany('INSERT INTO sales VALUES (?, ?, ?, ?)', sales_data)
conn.commit()

query = '''
SELECT category, product_id, total_sales, 
       RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rank
FROM (
    SELECT category, product_id, SUM(sales_amount) AS total_sales
    FROM sales
    GROUP BY category, product_id
) sub
ORDER BY category, rank
'''
cursor.execute(query)
results = cursor.fetchall()
print('Ranking products within each category:')
for row in results:
    print(row)

conn.close()
Line Notes
cursor.execute('''CREATE TABLE salesCreate sales table schema
cursor.executemany('INSERT INTO sales VALUES (?, ?, ?, ?)', sales_data)Insert sample data
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC)Assign rank within each category partition ordered by sales
GROUP BY category, product_idAggregate sales per product within category
import java.sql.*;

public class WindowFunctionRanking {
    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
        Statement stmt = conn.createStatement();
        stmt.execute("CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)");
        String[] inserts = {
            "INSERT INTO sales VALUES('P1', 'Electronics', 100, '2023-01-01')",
            "INSERT INTO sales VALUES('P2', 'Electronics', 150, '2023-01-02')",
            "INSERT INTO sales VALUES('P3', 'Books', 200, '2023-01-01')",
            "INSERT INTO sales VALUES('P4', 'Books', 50, '2023-01-03')"
        };
        for (String sql : inserts) stmt.execute(sql);
        String query = "SELECT category, product_id, total_sales, " +
                       "RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rank " +
                       "FROM (SELECT category, product_id, SUM(sales_amount) AS total_sales FROM sales GROUP BY category, product_id) sub " +
                       "ORDER BY category, rank";
        ResultSet rs = stmt.executeQuery(query);
        System.out.println("Ranking products within each category:");
        while (rs.next()) {
            System.out.println(rs.getString("category") + " | " + rs.getString("product_id") + " | " + rs.getDouble("total_sales") + " | Rank: " + rs.getInt("rank"));
        }
        rs.close();
        stmt.close();
        conn.close();
    }
}
Line Notes
stmt.execute("CREATE TABLE salesCreate sales table
for (String sql : inserts) stmt.execute(sql);Insert sample sales data
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC)Window function to rank products within categories
GROUP BY category, product_idAggregate sales per product-category pair
#include <iostream>
#include <sqlite3.h>

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
    for(int i = 0; i < argc; i++) {
        std::cout << azColName[i] << ": " << (argv[i] ? argv[i] : "NULL") << " | ";
    }
    std::cout << std::endl;
    return 0;
}

int main() {
    sqlite3 *db;
    char *errMsg = 0;
    int rc = sqlite3_open(":memory:", &db);
    if(rc) {
        std::cerr << "Can't open database: " << sqlite3_errmsg(db) << std::endl;
        return 1;
    }
    const char *create_sql = "CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT);";
    rc = sqlite3_exec(db, create_sql, 0, 0, &errMsg);
    const char *insert_sql = "INSERT INTO sales VALUES('P1','Electronics',100,'2023-01-01');"
                             "INSERT INTO sales VALUES('P2','Electronics',150,'2023-01-02');"
                             "INSERT INTO sales VALUES('P3','Books',200,'2023-01-01');"
                             "INSERT INTO sales VALUES('P4','Books',50,'2023-01-03');";
    rc = sqlite3_exec(db, insert_sql, 0, 0, &errMsg);
    const char *query = "SELECT category, product_id, total_sales, "
                        "RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rank "
                        "FROM (SELECT category, product_id, SUM(sales_amount) AS total_sales FROM sales GROUP BY category, product_id) sub "
                        "ORDER BY category, rank;";
    std::cout << "Ranking products within each category:" << std::endl;
    rc = sqlite3_exec(db, query, callback, 0, &errMsg);
    sqlite3_close(db);
    return 0;
}
Line Notes
sqlite3_exec(db, create_sql, 0, 0, &errMsg)Create sales table
sqlite3_exec(db, insert_sql, 0, 0, &errMsg)Insert sample data
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC)Window function for ranking within partitions
GROUP BY category, product_idAggregate sales per product-category
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');

db.serialize(() => {
  db.run("CREATE TABLE sales(product_id TEXT, category TEXT, sales_amount REAL, sale_date TEXT)");
  const stmt = db.prepare("INSERT INTO sales VALUES (?, ?, ?, ?)");
  stmt.run('P1', 'Electronics', 100, '2023-01-01');
  stmt.run('P2', 'Electronics', 150, '2023-01-02');
  stmt.run('P3', 'Books', 200, '2023-01-01');
  stmt.run('P4', 'Books', 50, '2023-01-03');
  stmt.finalize();

  const query = `
    SELECT category, product_id, total_sales,
           RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rank
    FROM (
      SELECT category, product_id, SUM(sales_amount) AS total_sales
      FROM sales
      GROUP BY category, product_id
    ) sub
    ORDER BY category, rank
  `;

  db.all(query, [], (err, rows) => {
    if (err) throw err;
    console.log('Ranking products within each category:');
    rows.forEach(row => {
      console.log(`${row.category} | ${row.product_id} | ${row.total_sales} | Rank: ${row.rank}`);
    });
  });
});

// Run with: node this_file.js
Line Notes
db.run("CREATE TABLE salesCreate sales table schema
stmt.run(...)Insert sample sales data
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC)Window function to rank products within categories
GROUP BY category, product_idAggregate sales per product-category pair
⏱
Complexity
TimeO(n log n) due to sorting inside window function
SpaceO(n) to hold intermediate aggregation and ranking data

Window functions require sorting partitions which can be costly but provide powerful analytics.

💡 For large datasets, window functions are optimized by databases but understanding their cost helps in query tuning.
Interview Verdict: Accepted - advanced and powerful approach

Mastering window functions is key to solving complex SQL interview problems involving ranking and analytics.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always start by explaining the brute force approach to show understanding, then code the SQL GROUP BY + HAVING approach for efficiency, and mention window functions for ranking if asked.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n + m log m)O(n)NoN/AMention only - never code due to inefficiency
2. SQL GROUP BY + HAVINGO(n)O(k) where k is distinct groupsNoN/ACode this approach - standard and efficient
3. Window Functions for RankingO(n log n)O(n)NoN/AMention and code if ranking required - advanced technique
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before your interview. Start by explaining the brute force approach to show your grasp of aggregation mechanics, then move to SQL GROUP BY and HAVING for efficiency, and finally demonstrate window functions for ranking.

How to Present

Step 1: Clarify the problem requirements and data schema.Step 2: Describe the brute force manual aggregation approach to show understanding.Step 3: Present the SQL GROUP BY and HAVING query for filtering aggregated data.Step 4: Explain window functions and write a query to rank products within categories.Step 5: Discuss edge cases and test your queries with sample data.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 10min → Test: 5min. Total ~23min

What the Interviewer Tests

The interviewer tests your understanding of SQL aggregation, filtering with HAVING, and advanced window functions for ranking. They also assess your ability to optimize queries and handle edge cases.

Common Follow-ups

  • How would you handle ties differently using DENSE_RANK() instead of RANK()?
  • Can you write a query to get the top N products per category?
💡 These follow-ups test your knowledge of window function variants and ability to extend queries for common business needs.
🔍
Pattern Recognition

When to Use

1) Need to summarize data by groups; 2) Need to filter groups based on aggregates; 3) Need to rank or number rows within groups; 4) Problem mentions GROUP BY, HAVING, or window functions.

Signature Phrases

'total sales per category''filter groups with aggregate condition''rank products within each category'

NOT This Pattern When

Simple SELECT queries without grouping or ranking, or problems focused on joins without aggregation

Similar Problems

SQL Aggregation Basics - foundational understanding of GROUP BYRanking Functions - mastering window functions for row numberingFiltering Aggregates with HAVING - conditional filtering of groups

Practice

(1/5)
1. In which scenario is MVCC most beneficial compared to traditional locking mechanisms in a database system?
easy
A. When strict serializability is required and no stale reads are allowed
B. When transactions are short-lived and mostly write-heavy with minimal reads
C. When many concurrent transactions perform mostly read operations with occasional writes
D. When the database workload consists primarily of batch updates with no concurrent reads

Solution

  1. Step 1: Understand MVCC's strength in concurrency

    MVCC allows readers to access snapshot versions without blocking writers, making it ideal for workloads with many reads and fewer writes.
  2. Step 2: Analyze write-heavy workloads

    Write-heavy workloads with minimal reads benefit less from MVCC since version maintenance overhead can be costly.
  3. Step 3: Analyze batch updates without concurrent reads

    Batch updates without concurrent reads do not leverage MVCC's snapshot isolation benefits.
  4. Step 4: Analyze strict serializability requirements

    MVCC provides snapshot isolation, which may allow some anomalies; strict serializability requires stronger mechanisms.
  5. Final Answer:

    Option C -> Option C
  6. Quick Check:

    MVCC shines in read-heavy concurrent environments [OK]
Hint: MVCC excels when many reads coexist with some writes
Common Mistakes:
  • Assuming MVCC is best for write-heavy workloads
  • Confusing snapshot isolation with strict serializability
  • Believing MVCC is unnecessary without concurrent reads
2. Why might a B-Tree be less efficient than a B+ Tree for disk-based database indexing despite having fewer levels in some cases?
medium
A. Because B-Tree stores data in all nodes, increasing node size and causing more disk I/O per node access.
B. Because B-Tree requires more frequent rebalancing during insertions, increasing CPU overhead.
C. Because B-Tree's height is always greater than B+ Tree, leading to more disk reads.
D. Because B-Tree's internal nodes contain data, making range queries slower due to lack of leaf node linkage.

Solution

  1. Step 1: Compare data storage impact on range queries

    B-Tree stores data in all nodes but lacks linked leaf nodes, making sequential range scans inefficient.
  2. Step 2: Understand disk I/O implications

    Range queries in B-Tree require traversing multiple nodes without sequential access, increasing disk I/O.
  3. Step 3: Analyze other options

    Because B-Tree stores data in all nodes, increasing node size and causing more disk I/O per node access. is incorrect because storing data in all nodes does not necessarily increase node size beyond B+ Tree's leaf nodes. Because B-Tree requires more frequent rebalancing during insertions, increasing CPU overhead. is misleading; both trees require rebalancing but it's not the main inefficiency. Because B-Tree's height is always greater than B+ Tree, leading to more disk reads. is false; B+ Tree height is often equal or less.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Lack of linked leaves in B-Tree slows range queries despite fewer levels.
Hint: Linked leaves -> efficient range queries; B-Tree lacks this -> slower range scans
Common Mistakes:
  • Assuming fewer levels always means fewer disk I/Os
  • Confusing node size impact on disk reads
  • Overestimating rebalancing cost differences
3. Which of the following statements about explain plans and cardinality estimates is INCORRECT?
medium
A. Explain plans help identify inefficient operations by showing estimated costs and row counts
B. Cardinality estimates in explain plans are predictions, not guaranteed counts
C. Explain plans always show the exact number of rows that will be processed at each step during execution
D. Cardinality estimates influence the optimizer's choice of join algorithms and access paths

Solution

  1. Step 1: Understand explain plan cardinality

    Explain plans show estimated--not actual--row counts before execution.
  2. Step 2: Analyze options

    Explain plans always show the exact number of rows that will be processed at each step during execution: Incorrect because explain plans show estimates, not exact counts.
    Cardinality estimates in explain plans are predictions, not guaranteed counts: Correct--estimates can differ from actuals.
    Explain plans help identify inefficient operations by showing estimated costs and row counts: Correct--explain plans reveal cost and cardinality to spot inefficiencies.
    Cardinality estimates influence the optimizer's choice of join algorithms and access paths: Correct--cardinality guides join and access path choices.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Explain plans provide estimates, not runtime exacts.
Hint: Explain plans show estimates, not exact execution metrics.
Common Mistakes:
  • Confusing estimated cardinality with actual runtime row counts
  • Assuming explain plans reflect real-time execution
  • Ignoring the predictive nature of cost and cardinality
4. Identify the bug in the following Python code that attempts to find customers with orders using a JOIN simulation:
medium
A. Line 3: The return statement should remove duplicates explicitly with DISTINCT.
B. Line 2: The list comprehension incorrectly filters customers by order IDs instead of customer IDs.
C. Line 1: Using a list instead of a set for order_customer_ids causes duplicates and slower lookups.
D. Line 1: The variable name order_customer_ids is misleading and should be order_ids.

Solution

  1. Step 1: Analyze data structure used for order_customer_ids

    Using a list causes O(m) lookup per customer, leading to O(n*m) time and duplicates.
  2. Step 2: Identify impact on performance and correctness

    Using a set enables O(1) lookups and avoids duplicates, improving performance and correctness.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Replacing list with set fixes performance and duplicate issues [OK]
Hint: Sets provide O(1) membership checks, lists do not [OK]
Common Mistakes:
  • Using list instead of set for membership
  • Confusing customer_id with order_id
  • Assuming duplicates are handled automatically
5. Given a set of functional dependencies where some attributes can be NULL, how does the presence of NULL values affect the application of Armstrong's Axioms in inferring new dependencies?
hard
A. Armstrong's Axioms assume attributes are non-nullable, so NULLs invalidate inference results
B. NULL values do not affect Armstrong's Axioms since they are logical inference rules independent of data instances
C. Augmentation fails in presence of NULLs because adding attributes with NULL breaks dependency preservation
D. Transitivity cannot be applied if any attribute in the chain can be NULL

Solution

  1. Step 1: Understand Armstrong's Axioms nature

    They are inference rules based on attribute sets and dependencies, independent of actual data values.
  2. Step 2: Analyze NULL impact

    NULLs affect data instance semantics but do not change logical inference of dependencies.
  3. Step 3: Evaluate options

    A is incorrect; axioms do not assume non-nullability.
    B is correct; inference is logical and data-agnostic.
    C is incorrect; augmentation is a logical rule, not data-dependent.
    D is incorrect; transitivity applies regardless of NULL presence.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Armstrong's Axioms operate on schema-level dependencies, not data values.
Hint: Armstrong's Axioms = logical rules, unaffected by NULLs
Common Mistakes:
  • Confusing logical inference with data instance behavior
  • Assuming NULLs break functional dependencies at schema level
  • Believing augmentation or transitivity depend on data completeness
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard