Search code examples
sqlsql-server

TOP or Alternative to return most recent 3 Months data in SQL


I wish to return the data for Feb 2025, Jan 2025 and Dec 2024 on a report using SQL Server.

Please advise where I have gone wrong:

This is what I have and a demo table:

CREATE TABLE myTable(id int, area varchar(20), myDate DATE, Qty 
int);
INSERT INTO myTable ([id],[area],[myDate],[Qty])
VALUES
(1,'Shipping','2024-12-24',3),
(2,'IT','2024-12-15', 5),
(3,'Shipping','2024-12-26',6),
(4,'HR','2024-12-28',2),
(5,'IT','2024-12-30',1),
(6,'Shipping','2025-01-01',2),
(7,'Shipping','2025-01-03',1),
(8,'Shipping','2025-02-01',2),
(9,'Shipping','2024-11-28',1);

When I use TOP and ORDER BY ASC:

SELECT TOP(3) YEAR(myDate) [Year], MONTH(myDate) [Month],
DATENAME(MONTH,myDate) [Month Name], Sum(Qty) [Total]
FROM myTable WHERE area = 'Shipping'
GROUP BY YEAR(myDate), MONTH(myDate),
DATENAME(MONTH, myDate)
ORDER BY 1,2 ASC

I get this:

 Year        Month       Month Name                        Total

   2024          11    November                              1
   2024          12    December                              9
   2025           1    January                               4

When I use TOP and ORDER BY DESC like this:

SELECT TOP(3) YEAR(myDate) [Year], MONTH(myDate) [Month],
DATENAME(MONTH,myDate) [Month Name], Sum(Qty) [Total]
FROM myTable WHERE area = 'Shipping'
GROUP BY YEAR(myDate), MONTH(myDate),
DATENAME(MONTH, myDate)
ORDER BY 1,2 DESC

I get this:


Year        Month       Month Name                     Total

   2024          12    December                              9
   2024          11    November                              1
   2025           2    February                              2

Desired output like so:


   Year        Month       Month Name                     Total

   2024          12         December                         9
   2025           1         January                          4
   2025           2         February                         2

I don't want it hardcoded for those months, I will need the three most recent months every month.


Solution

  • Why not pre-filter in the WHERE clause?

    SELECT YEAR(myDate) [Year], MONTH(myDate) [Month],
        DATENAME(MONTH,myDate) [Month Name], Sum(Qty) [Total]
    FROM myTable 
    WHERE area = 'Shipping'
        AND myDate >= '2024-12-01'
    GROUP BY YEAR(myDate), MONTH(myDate),
    DATENAME(MONTH, myDate)
    ORDER BY [Year], [Month] DESC
    

    https://dbfiddle.uk/Ml6t8SbB

    This is better than TOP 3 because the TOP 3 strategy requires computing groups for the entire table before it can decide which rows to use. This way, you can decide which rows matter, likely with the help of an index, and then only need to compute those groups.

    The trick is if you always want to base this on the current date, since you want to see February data even though it is not yet February. Clearly we want the date to tick into the next month a little early. I suspect you want the most recent month for which you have data, in which case you can do this with a nested SELECT and the EOMonth() function:

    SELECT YEAR(myDate) [Year], MONTH(myDate) [Month],
        DATENAME(MONTH,myDate) [Month Name], Sum(Qty) [Total]
    FROM myTable 
    WHERE area = 'Shipping'
         AND myDate > (SELECT dateadd(month, -3, EOMonth(MAX(myDate))) FROM myTable)
    GROUP BY YEAR(myDate), MONTH(myDate), DATENAME(MONTH, myDate)
    ORDER BY [YEAR] DESC, [Month] DESC;
    

    https://dbfiddle.uk/-4OCpm0f

    Finally, it's poor practice to order by column ordinals like that.