Practice Set: Window Functions and Joins
Explore SQL problems focused on window functions, joins, and ranking to build skills in handling running totals, leaderboards, referral analysis, and duplicate detection. Learn how to write correct queries for interview scenarios, understand common pitfalls, and validate output to ensure accurate insights in data science.
We'll cover the following...
- Problem 1: Top creators by follower growth
- Solution
- Problem 2: Daily activity and signup totals
- Solution
- Problem 3: Referrals followed by purchases
- Solution
- Problem 4: Cumulative spend and latest order
- Solution
- Problem 5: Potential duplicate accounts
- Solution
- Problem 6: Creator leaderboard scorecard
- Solution
- Check yourself
This practice set covers six SQL problems involving ranking, running totals, and joins across multiple tables: a top-N leaderboard, a rolling daily metric, referral analysis using a self join and an additional join, cumulative spending, duplicate-account detection, and a three-part scorecard with progressively challenging follow-up questions. For each problem, predict the output or correct the query before reviewing the hints and complete solution.
The running example is a creator leaderboard, which must rank follower growth within each category without turning a many-category creator into duplicated follower counts. Every problem uses one SQLite schema. These are the tables and the columns the problems actually use:
users(user_id, signup_date, referred_by_user_id, email_hash, device_id): One row per account.referred_by_user_idpoints to the user who referred this one (Problem 3), andemail_hashanddevice_idare the duplicate-account signals (Problem 5).orders(order_id, user_id, amount, created_at): One row per order (Problems 3 and 4).events(event_id, user_id, event_time): One row per activity event (Problem 2).follows(follow_id, creator_id, user_id, followed_at): One row per follow. Both IDs are user IDs:creator_idis the account being followed, anduser_idis the follower (Problems 1 and 6).creator_categories(creator_id, category_id): One row per creator and category, so a creator can belong to several categories.categories(category_id, category_name): One row per category.
A creator is simply a user with followers, so there is no separate creators table. Some problems use users and orders directly, but the grain check stays the same: name the row represented at every stage.
Problem 1: Top creators by follower growth
Scenario: Return the top three creators by follower growth this month within each category. Include tied creators at third place.
A near-correct answer aggregates follows, joins categories, computes a rank, then attempts to filter the rank in WHERE.
SELECTcc.category_id,f.creator_id,COUNT(*) AS gained_followers,RANK() OVER (PARTITION BY cc.category_idORDER BY COUNT(*) DESC) AS category_rankFROM follows AS fJOIN creator_categories AS ccON cc.creator_id = f.creator_idWHERE f.followed_at >= date('now', 'start of month')AND category_rank <= 3GROUP BY cc.category_id, f.creator_id;
The query fails because SQLite cannot filter a window result in the same query block’s WHERE clause.
Question: How would you rewrite this query to return the top three creators by follower growth in each category, including ties at third place?
Solution
The query below ranks creators within each category using one row per creator.
WITH monthly_gains AS (SELECTcreator_id,COUNT(*) AS gained_followersFROM followsWHERE followed_at >= date('now', 'start of month')GROUP BY creator_id),ranked_creators AS (SELECTcat.category_name,mg.creator_id,mg.gained_followers,RANK() OVER (PARTITION BY cc.category_idORDER BY mg.gained_followers DESC) AS category_rankFROM monthly_gains AS mgJOIN creator_categories AS ccON cc.creator_id = mg.creator_idJOIN categories AS catON cat.category_id = cc.category_id)SELECTcategory_name,creator_id,gained_followers,category_rankFROM ranked_creatorsWHERE category_rank <= 3ORDER BY category_name, category_rank, creator_id;
Code explanation:
monthly_gains: One row per creator, counting the follows since the start of the month, so every creator has a single follower count before any join.ranked_creators: Joins that count tocreator_categoriesandcategories, so a creator in two categories appears once per category with the same count.RANK()numbers the creators inside each category, and tied creators share a rank.SELECT: Filterscategory_rank <= 3in the outer query, because a window result cannot be filtered in theWHEREclause of the block that computes it.
The solution joins three tables in order: aggregated follows to creator_categories on creator_id, then creator_categories to categories on category_id, so the leaderboard shows each category by name. Both joins happen after monthly_gains has reached one row per creator, so a creator in two categories appears once per category with the same ...