🚀 SQL Challenge: Month-Over-Month Revenue Growth! 📈
Who’s Ready to Level Up Their SQL Game?
Hey LinkedIn Network! 👋
If you’ve ever struggled with SQL problems that test your ability to analyse trends over time, this article is for you. Today, we’re dissecting a real-world SQL challenge that’s a favourite in data interviews and business analytics roles: calculating month-over-month revenue growth.
But this isn’t just another coding tutorial. It’s about:
🔹 Thinking like a data pro – mastering date manipulations and window functions.
🔹 Writing interview-ready SQL – clean, optimised code that stands out to hiring managers.
🔹 Solving problems that matter – skills you’ll use in financial reporting, product analytics, and more.
Why This Will Transform Your SQL Skills
Short posts are great for quick wins, but career growth demands deep, practical learning. By the end of this guide, you’ll:
✅ Solve complex SQL problems faster – Learn how to structure queries for scalability.
✅ Avoid rookie mistakes – like mishandling edge cases or writing unreadable queries.
✅ Interview-ready techniques – Discover how to explain your logic clearly (a skill recruiters love!).
Let’s Collaborate!
This isn’t a lecture—it’s a conversation. As you read:
💬 Drop a comment with your solution or a question—let’s learn together! Compare your approach with peers and refine your strategy.
🔗 Tag a peer – Who else would benefit from this breakdown?
🚀 Share your “aha!” moment – did you spot an optimisation I missed?
Ready to dive in? Let’s go! ⬇️
What’s the SQL Challenge?
Objective: Calculate the month-over-month (MoM) percentage change in revenue using transaction data from the sf_transactions table.
Key Requirements:
Breakdown of the sf_transactions table structure:
Pro Tip: Always validate assumptions about keys and relationships with stakeholders or documentation!
Why Is This Problem Tricky?
How Does the SQL Query Evolve?
Below is a step-by-step walkthrough of the SQL solution to calculate the month-over-month percentage change in revenue.
Step 1: Explore Raw Data
Query:
SELECT id, created_at, value, purchase_id
FROM sf_transactions
LIMIT 10;
Purpose: View raw transactional data.
Sample Output:
Step 2: Extract Year-Month
Query:
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo,
value
FROM sf_transactions;
Purpose: Convert created_at to a YYYY-MM format.
Sample Output:
Step 3: Aggregate Monthly Revenue
Query:
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo,
SUM(value) AS total_revenue
FROM sf_transactions
GROUP BY yr_mo;
Purpose: Calculate total revenue per month.
Sample Output:
Step 4: Fetch Previous Month’s Revenue
Query:
WITH monthly_revenue AS (
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo,
SUM(value) AS total_revenue,
LAG(SUM(value)) OVER (ORDER BY DATE_FORMAT(created_at, '%Y-%m')) AS prev_month_revenue
FROM sf_transactions
GROUP BY yr_mo
)
SELECT * FROM monthly_revenue;
Purpose: Compare each month’s revenue to the prior month.
Sample Output:
Step 5: Calculate Percentage Change
Query:
WITH monthly_revenue AS (
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo,
SUM(value) AS total_revenue,
LAG(SUM(value))
OVER (ORDER BY DATE_FORMAT(created_at, '%Y-%m')) AS prev_month_revenue
FROM sf_transactions
GROUP BY yr_mo
)
SELECT
yr_mo,
ROUND(
(total_revenue - prev_month_revenue)
/ NULLIF(prev_month_revenue, 0) * 100,
2
) AS revenue_change_pct
FROM monthly_revenue
ORDER BY yr_mo;
Purpose: Final result with MoM % change.
Sample Output:
Keyword Explanations with Syntax, Tips, and Examples
1. DATE_FORMAT()
Purpose: Converts a date/time value into a specified string format. Syntax:
DATE_FORMAT(date_column, format_string)
Parameters:
format specifiers:
Example:
SELECT
created_at,
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo
FROM sf_transactions;
Tips:
SELECT STR_TO_DATE('2023-Jan', '%Y-%b'); -- Returns 2023-01-01
2. CTE (Common Table Expression)
Purpose: Creates a temporary named result set for use within a query. Syntax:
WITH cte_name AS (
SELECT ... FROM ...
)
SELECT * FROM cte_name;
Tips:
WITH cte1 AS (...), cte2 AS (SELECT * FROM cte1) ...
3. GROUP BY
Purpose: Aggregates rows into summary rows based on shared column values. Syntax:
SELECT aggregate_function(column), grouped_column
FROM table
GROUP BY grouped_column;
Example:
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS yr_mo,
COUNT(id) AS transactions,
SUM(value) AS total_revenue
FROM sf_transactions
GROUP BY yr_mo;
Tips:
GROUP BY yr_mo
HAVING total_revenue > 1000;
4. LAG()
Purpose: Accesses data from a previous row in the result set (window function). Syntax:
LAG(column, offset, default_value) OVER (
[PARTITION BY partition_column]
ORDER BY order_column
)
Parameters:
Example:
SELECT
yr_mo,
total_revenue,
LAG(total_revenue, 1, 0) OVER (ORDER BY yr_mo) AS prev_month
FROM monthly_revenue;
Tips:1
LAG() vs LEAD() with Offset=3: Unified Table & Explanation
LAG(column_name, 3):
LEAD(column_name, 3):
Key Analogies
Why This Works
Key Takeaway
LAG/LEAD offsets act like time travel relative to the current row:
What’s Your Approach to This Problem?
Take the Challenge: Rebuild the query using a date spine to include months with no transactions (treat missing values as zero). Remember, failing to handle division by zero (use NULLIF) or to account for missing months can break your analysis.
Share Your Solution: Post your optimized query in the comments—we’ll highlight the most efficient approaches!
Collaborate: Tag a data engineer or analyst who might find this useful.
Follow for More:
🔔 Follow me on LinkedIn for weekly SQL deep dives and insights.
🚀 Ready for the next challenge? Let’s continue to conquer SQL together!