I am looking to find the sum of the current + the last X Nth Rows. I am able to do this with the following query, however it is not very scalable.
SELECT [id], [amount] + LAG([amount],6) OVER(ORDER BY [id]) + LAG([amount],12) OVER(ORDER BY [id]) + LAG([amount],18) OVER(ORDER BY [id])
If this example, I’m finding the current value of “amount”, plus the last 3 “amounts” split 6 apart:
- X = 3
- N = 6
I will be using these within dynamic queries and would prefer not to build such a complex query each time. There could be many “lags” in some of the queries. Is there another way to write this query that would be more scalable?
SOURCE DATA
| ID | Amount |
|---|---|
| 1 | 107.35 |
| 2 | 105.41 |
| 3 | 104.63 |
| 4 | 106.7 |
| 5 | 108.7 |
| 6 | 110.21 |
| 7 | 108.8 |
| 8 | 108.91 |
| 9 | 108.5 |
| 10 | 106.66 |
| 11 | 105.2 |
| 12 | 106.5 |
| 13 | 108.27 |
| 14 | 109.72 |
| 15 | 111.53 |
| 16 | 112.8 |
| 17 | 109.03 |
| 18 | 115.31 |
| 19 | 115.56 |
| 20 | 116.85 |
| 21 | 116.08 |
| 22 | 117.61 |
| 23 | 118.31 |
| 24 | 119.25 |
| 25 | 118.45 |
| 26 | 118.43 |
| 27 | 120.16 |
| 28 | 122.5 |
| 29 | 125.57 |
| 30 | 125.65 |
EXPECTED RESULTS
| ID | SUM OF LAST 4 |
|---|---|
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | NULL |
| 5 | NULL |
| 6 | NULL |
| 7 | NULL |
| 8 | NULL |
| 9 | NULL |
| 10 | NULL |
| 11 | NULL |
| 12 | NULL |
| 13 | NULL |
| 14 | NULL |
| 15 | NULL |
| 16 | NULL |
| 17 | NULL |
| 18 | NULL |
| 19 | 439.98 |
| 20 | 440.89 |
| 21 | 440.74 |
| 22 | 443.77 |
| 23 | 441.24 |
| 24 | 451.27 |
| 25 | 451.08 |
| 26 | 453.91 |
| 27 | 456.27 |
| 28 | 459.57 |
| 29 | 458.11 |
| 30 | 466.71 |
Advertisement
Answer
At a best guess, it seems like what you want is something like this:
DECLARE @X int = 3,
@N int = 6;
SELECT YT.ID,
YT.Amount,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < @X+1 THEN NULL
ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
END
FROM dbo.YourTable YT
CROSS APPLY (VALUES(ID % @N))G(Grp)
ORDER BY YT.ID;
You’ll note, however, that the 3 is hardcoded in one place, as you cannot use a variable for the ROWS BETWEEN clause. If you need to parametrise this, you’ll need to use dynamic SQL:
DECLARE @X int = 3,
@N int = 6;
DECLARE @SQL nvarchar(MAX),
@CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET @SQL = CONCAT(N'SELECT YT.ID,', @CRLF,
N' YT.Amount,', @CRLF,
N' CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < @X+1 THEN NULL', @CRLF,
N' ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID', @CRLF,
N' ROWS BETWEEN ',@X,N' PRECEDING AND CURRENT ROW)', @CRLF, --I don't like injecting raw values, but if @X is an int, it is "safe"
N' END', @CRLF,
N'FROM dbo.YourTable YT', @CRLF,
N' CROSS APPLY (VALUES(ID % @N))G(Grp)', @CRLF,
N'ORDER BY YT.ID;');
PRINT @SQL; --Your best debugging friend
EXEC sys.sp_executesql @SQL, N'@X int, @N int', @X, @N;