Write a query to compute year-over-year revenue comparison for each region using self-joins.
-
SQL
- Write a SQL query to find the second highest salary from an Employees table without using LIMIT or TOP.
- Write a query to find all customers who placed orders in every month of 2025.
- Write a query to identify duplicate rows in a Transactions table based on customer_id, amount, and date.
- Write a query using GROUP BY and HAVING to find departments with more than 10 employees earning above the department average salary.
- Write a query to return the top 3 selling products per category using a subquery (no window functions allowed).
- Write a query to calculate the running total of daily revenue for a store using a self-join.
- Write a query to find the average order value per customer segment, excluding cancelled orders.
- Write a query to find employees who earn more than their direct manager, using a self-join on an Employees table.
- Write a query to list all customers who have not placed any order in the last 90 days.
- Write a query to compute the percentage of total revenue contributed by each product category.
- Write a query to find the first purchase date and last purchase date for every customer.
- Write a query to find the three most recent orders for each customer.
- Write a query that returns customers whose total spend is above the 90th percentile of all customers.
- Write a query to find customers who bought Product A but never bought Product B.
- Write a query to count how many orders each customer placed and rank customers by order count within their city.
- Write a query to identify the busiest hour of the day for support ticket creation using a Tickets table.
- Write a query to find the top 5 referral sources by conversion rate from a Signups table.
- Write a query using EXISTS to find suppliers who have never had a late delivery.
- Write a query to detect gaps in a sequential invoice_number column.
- Write a query to find all products whose price increased in the last price update compared to the previous one.
- Write a query to calculate month-over-month growth rate in active users from a DailyActiveUsers table.
- Write a query to return the department with the highest average tenure of employees.
- Write a query to find all orders placed on weekends versus weekdays and compare average order value.
- Write a query to find customers who upgraded their subscription plan more than once in the last year.
- Write a query to return the top 10% of sales reps by revenue generated in Q1.
- Write a query using a CTE to calculate a 7-day moving average of website visits.
- Write a query to find all patients who missed at least two scheduled appointments in the past year.
- Write a query to identify employees who have never taken a single day of leave in the last two years.
- Write a query to compute year-over-year revenue comparison for each region using self-joins.
- Write a query to find the longest streak of consecutive days a user logged into an app.
- Write a query to identify vendors whose invoice amounts differ by more than 15% from their historical average.
- Write a query to find all students who scored above the class average in more than 3 subjects.
- Write a query to detect accounts with more than 5 failed login attempts within a 10-minute window.
- Write a query to find the second most frequently ordered item for each customer.
- Write a query to identify products frequently bought together, based on an OrderItems table.
- Write a query to find the average time between a support ticket being opened and first responded to, grouped by priority.
- Write a query to find the top-selling product in each store location for the current quarter.
- Write a query to list all foreign keys referencing a given table in a schema (engine-specific system tables allowed).
- Write a query to compute the cumulative distinct count of new customers acquired each month.
Answer
Self-join each region's current-year revenue to its own prior-year revenue by matching on region and an offset year.
Solution
SELECT curr.region, curr.year, curr.revenue AS curr_year_revenue,
prev.revenue AS prev_year_revenue,
ROUND(100.0 * (curr.revenue - prev.revenue) / prev.revenue, 2) AS yoy_growth_pct
FROM (
SELECT region, YEAR(sale_date) AS year, SUM(amount) AS revenue
FROM Sales GROUP BY region, YEAR(sale_date)
) curr
JOIN (
SELECT region, YEAR(sale_date) AS year, SUM(amount) AS revenue
FROM Sales GROUP BY region, YEAR(sale_date)
) prev
ON curr.region = prev.region AND curr.year = prev.year + 1;Step by Step
- Both
currandprevare the same aggregated subquery: total revenue per region per year. - The self-join condition
curr.year = prev.year + 1pairs each year's row with the row from exactly one year earlier, for the same region. - Growth percentage is
(current - previous) / previous, exactly as in the earlier month-over-month growth question, just applied at a yearly grain.
Alternative Method: LAG() Window Function
WITH yearly AS (
SELECT region, YEAR(sale_date) AS year, SUM(amount) AS revenue
FROM Sales GROUP BY region, YEAR(sale_date)
)
SELECT region, year, revenue,
LAG(revenue) OVER (PARTITION BY region ORDER BY year) AS prev_year_revenue,
ROUND(100.0 * (revenue - LAG(revenue) OVER (PARTITION BY region ORDER BY year))
/ LAG(revenue) OVER (PARTITION BY region ORDER BY year), 2) AS yoy_growth_pct
FROM yearly;LAG() is generally the more idiomatic modern approach - it avoids the self-join entirely and handles any gaps in years more gracefully (a self-join on year + 1 would simply produce no match for a missing year, while LAG() would compare against whatever the actual previous available year happens to be, which can be a subtle but important difference).
Key Exam Traps
- Self-join on year + 1 assumes no missing years - if a region has a gap year with zero sales, the self-join simply produces no row for that comparison, while
LAG()would incorrectly compare against a year that isn't actually adjacent. Both approaches need this edge case flagged explicitly. - First year for any region has no valid prior-year match - the self-join naturally excludes it (no matching row), and the
LAG()version returnsNULLfor it - both are correct, just expressed differently. - Region renames or restructuring over time (e.g. a region split into two) would break the year-over-year comparison silently, since past and current data would no longer share the same region key - a real-world data quality concern worth mentioning.