Year-over-Year Growth in SQL

Year-over-year growth compares each month with the same month a year earlier, which removes seasonality from the comparison.

Example query

This example assumes: orders(id, order_date date, total_amount numeric). It is written in PostgreSQL syntax, and your table and column names will differ from these.

WITH monthly AS (
  SELECT DATE_TRUNC('month', order_date)::date AS month,
         SUM(total_amount) AS sales
  FROM orders
  GROUP BY 1
)
SELECT cur.month,
       cur.sales,
       prev.sales AS sales_last_year,
       ROUND(100.0 * (cur.sales - prev.sales) / NULLIF(prev.sales, 0), 1) AS yoy_growth_pct
FROM monthly cur
LEFT JOIN monthly prev
  ON prev.month = (cur.month - INTERVAL '1 year')::date
ORDER BY cur.month;

How it works

  • The same monthly CTE is joined to itself: each row is matched to the row exactly one year earlier.
  • A LEFT JOIN keeps the first year, where there is no prior year, with nulls in the comparison columns.
  • The growth is the difference divided by last year's figure; NULLIF guards against a zero base.

MySQL, BigQuery, SQL Server and SQLite differences

  • A self-join on date arithmetic works everywhere; write the year offset as DATE_SUB(month, INTERVAL 1 YEAR) in MySQL, DATE_SUB(month, INTERVAL 1 YEAR) in BigQuery, or DATEADD(year, -1, month) in SQL Server.
  • LAG(sales, 12) is a shortcut only if every month is present.

Common mistakes

  • LAG(sales, 12) silently compares the wrong months if any month is missing; the self-join does not.
  • Leap-year and partial-month effects distort February and the current month.
  • A promotion or a price change last year makes the comparison base unusual; look at the level, not only the growth.

Ask for it in plain English

Instead of writing the query yourself, you can type the question. SchemaChat turns it into SQL against your connected database or uploaded file, runs it, and shows the SQL and the figures it used so you can check the answer before you share it. Check that the query matches the definition your business uses.

  • Compare each month's sales with the same month last year.
  • What is our year-over-year growth for the last 6 months?

Frequently asked questions

What is year-over-year growth in SQL?
Year-over-year growth compares each month with the same month a year earlier, which removes seasonality from the comparison.
How does the year-over-year growth query work?
The same monthly CTE is joined to itself: each row is matched to the row exactly one year earlier. A LEFT JOIN keeps the first year, where there is no prior year, with nulls in the comparison columns. The growth is the difference divided by last year's figure; NULLIF guards against a zero base.
What commonly goes wrong with year-over-year growth in SQL?
LAG(sales, 12) silently compares the wrong months if any month is missing; the self-join does not.