Growth compares this year’s year-to-date total with last year’s over the same dates: (this year − last year) / last year. The formula fits on one line. The work is in the rows where it does not apply, such as a product that did not exist last year.

A small example

Three products. Apples sold in both years, Pears are new this year and Plums sold only last year. The orders after 15 May fall outside the comparison in both years.

CREATE TEMP TABLE Sales (OrderDate DATE, Product VARCHAR, Amount NUMBER(10,2));
INSERT INTO Sales VALUES
  ('2025-02-10', 'Apples', 90),  ('2025-04-20', 'Apples', 110),
  ('2025-05-16', 'Apples', 500), ('2025-03-01', 'Plums', 60),
  ('2026-01-15', 'Apples', 120), ('2026-04-02', 'Apples', 130),
  ('2026-03-10', 'Pears', 70),   ('2026-05-20', 'Apples', 400);

The query

The periods are the same as in how to compare YTD with the same period last year: 1 January to 15 May of each year.

WITH Params AS (
  SELECT '2026-05-15'::DATE AS AsOf   -- CURRENT_DATE() in a live report
),
Periods AS (
  SELECT 'This year' AS Period,
         DATE_TRUNC('year', AsOf) AS StartDate,
         AsOf AS EndDate
  FROM Params
  UNION ALL
  SELECT 'Last year',
         DATE_TRUNC('year', DATEADD(year, -1, AsOf)),
         DATEADD(year, -1, AsOf)
  FROM Params
),
Totals AS (
  SELECT s.Product,
         SUM(IFF(p.Period = 'This year', s.Amount, 0)) AS YTD,
         SUM(IFF(p.Period = 'Last year', s.Amount, 0)) AS YTDLastYear
  FROM Periods p
  JOIN Sales s
    ON  s.OrderDate >= p.StartDate
    AND s.OrderDate <  DATEADD(day, 1, p.EndDate)
  GROUP BY ROLLUP (s.Product)
)
SELECT COALESCE(t.Product, 'Total') AS Product,
       t.YTD,
       t.YTDLastYear,
       ROUND(100 * (t.YTD - t.YTDLastYear) / NULLIF(t.YTDLastYear, 0), 1) AS GrowthPct
FROM Totals t
ORDER BY t.Product NULLS LAST;
Product YTD YTDLastYear GrowthPct
Apples 250.00 200.00 25.0
Pears 70.00 0.00 NULL
Plums 0.00 60.00 -100.0
Total 320.00 260.00 23.1

Why not DIV0

DIV0(a, b) returns 0 when b is 0. For growth that would show Pears at 0%, “no change”, which is the opposite of what happened. Keep the NULL and let the report decide how to show it.

Ratio or percentage

If the result goes to a BI tool that formats percentages, return the ratio, (YTD - YTDLastYear) / NULLIF(YTDLastYear, 0), without 100 * and without ROUND. The tool multiplies by 100 itself, and a number rounded in SQL loses precision in any later calculation.

A negative base

Refunds and returns can make last year’s net total negative. With −50 last year and 20 this year, the formula gives −140%, a decline, although the result improved by 70. Dividing by ABS(YTDLastYear) gives +140%, so the sign follows the direction of the change. Many teams prefer to show no percentage at all when the base is negative.

References