SUM relative to row date value

  • Dear all,

    With a table like this shown below, I'd like to use the Yearmonth value and then sum the last 3 months (from that yearmonth backwards) of Metric1 values, to create a new value Metric2 for that row.

    Yearmonth, Metric1, Metric2

    2010/01/01, 1, 0

    2010/02/01, 2, 0

    2010/03/01, 3, 0

    2010/04/01, 4, 0

    So the SQL code would change the table to be:

    Yearmonth, Metric1, Metric2

    2010/01/01, 1, 1

    2010/02/01, 2, 3

    2010/03/01, 3, 6

    2010/04/01, 4, 9

    There is only 1 row per month (these are always set to the first day of the month).

    I wonder if anyone has a nice way of doing this ?

    Regards, Greg

  • How's this?

    --First, lets build some test data

    DECLARE @TABLE AS TABLE (Yearmonth DATETIME, Metric1 INT, Metric2 INT)

    INSERT INTO @TABLE (Yearmonth, Metric1)

    SELECT '2010/01/01', 1 UNION ALL

    SELECT '2010/02/01', 2 UNION ALL

    SELECT '2010/03/01', 3 UNION ALL

    SELECT '2010/04/01', 4

    --Now lets get to work :)

    UPDATE t1

    SET Metric2 = ISNULL(RunningTotal,0)

    FROM @TABLE t1

    LEFT OUTER JOIN (SELECT Yearmonth, SUM(Metric1) AS RunningTotal

    FROM (SELECT A.Yearmonth AS Yearmonth, B.Yearmonth AS Yearmonth_B,

    B.Metric1

    FROM (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM @TABLE) A

    CROSS JOIN (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM @TABLE) B

    WHERE (B.ID >= A.ID-2 AND B.ID <= A.ID) ) T

    GROUP BY Yearmonth) tot ON t1.Yearmonth=tot.Yearmonth

    WHERE tot.Yearmonth = t1.Yearmonth

    SELECT * FROM @TABLE

    --edit-- Sorry, wrote it as a SELECT statement. Changed to an UPDATE statement now.


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

  • What would be the value for month 5? Is the value 9 correct for month 4?

    DROP TABLE #Sample

    CREATE TABLE #Sample (Yearmonth DATE, Metric1 INT, Metric2 INT)

    INSERT INTO #Sample (Yearmonth, Metric1, Metric2)

    SELECT '2010/01/01', 1, NULL UNION ALL

    SELECT '2010/02/01', 2, NULL UNION ALL

    SELECT '2010/03/01', 3, NULL UNION ALL

    SELECT '2010/04/01', 4, NULL UNION ALL

    SELECT '2010/05/01', 5, NULL

    SELECT * FROM #Sample

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • ChrisM@Work (8/31/2011)


    What would be the value for month 5? Is the value 9 correct for month 4?

    DROP TABLE #Sample

    CREATE TABLE #Sample (Yearmonth DATE, Metric1 INT, Metric2 INT)

    INSERT INTO #Sample (Yearmonth, Metric1, Metric2)

    SELECT '2010/01/01', 1, NULL UNION ALL

    SELECT '2010/02/01', 2, NULL UNION ALL

    SELECT '2010/03/01', 3, NULL UNION ALL

    SELECT '2010/04/01', 4, NULL UNION ALL

    SELECT '2010/05/01', 5, NULL

    SELECT * FROM #Sample

    Assuming I've understood the OP correctly, month 5 would be "12": -

    Month 5 + Month 4 + Month 3 = 12.


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

  • Confirming this:

    Month 4 would have metric2 as 9 - cos it would be 4+3+2.

    Month 5 would have metric2 as 12 (5+4+3).

    The problem would be that (and this is my fault for not explaining the test data well enough) there could be lots of Yearmonth rows and with some missing, so UNIONING them all may not be the best solution.

    Regards, Greg

  • greg.bull (8/31/2011)


    Confirming this:

    Month 4 would have metric2 as 9 - cos it would be 4+3+2.

    Month 5 would have metric2 as 12 (5+4+3).

    The problem would be that (and this is my fault for not explaining the test data well enough) there could be lots of Yearmonth rows and with some missing, so UNIONING them all may not be the best solution.

    Regards, Greg

    Did you check my solution? It works as you described. The "UNIONING" is to create test data since you didn't provide any, the actual code is below and written as an UPDATE statement.

    --EDIT--

    When you say some missing, what do you mean? Do you intend to SUM the last three known results, or are you only wanting to SUM the last 3 continuous rows?

    e.g.

    If we take the following data: -

    DECLARE @TABLE AS TABLE (Yearmonth DATETIME, Metric1 INT, Metric2 INT)

    INSERT INTO @TABLE (Yearmonth, Metric1)

    SELECT '2010/01/01', 1 UNION ALL

    SELECT '2010/02/01', 2 UNION ALL

    SELECT '2010/03/01', 3 UNION ALL

    SELECT '2010/04/01', 4 UNION ALL

    SELECT '2010/06/01', 5

    What would month "2010/06/01" be? Would you want it to be 12? Or would you want it to be 9 (as there is no "2010/05/01", the last three continuous results would be "2010/06/01" and "2010/04/01") ?


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

  • UPDATE #Sample SET Metric2 = ISNULL(NULLIF(Metric1+Metric1-1+Metric1-2,0),1)

    πŸ˜‰

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Right,

    I was a bit confused re: the test data. I guess it's an easy way to insert it, as I didn't provide the insert values.

    On to the next point, if a month row is missing, then it'd be ignored, so for month 6, we'd be summing month 6, month 5 (which'd be null) and month 4. We wouldn't want to find another month going back a bit more with non null data.

    So I will go and try your sample code - thanks for your help.

    G

  • Ah, thanks. The numbers don't run sequentially, I should have randomised my test data to make it more realistic. Sorry.

    Regards, Greg

  • greg.bull (8/31/2011)


    On to the next point, if a month row is missing, then it'd be ignored, so for month 6, we'd be summing month 6, month 5 (which'd be null) and month 4. We wouldn't want to find another month going back a bit more with non null data.

    Ah, well this needs cleaning up but it gets the result you're after. I'm sure someone will make a much more efficient version πŸ™‚

    --First, lets build some test data

    DECLARE @TABLE AS TABLE (Yearmonth DATETIME, Metric1 INT, Metric2 INT)

    INSERT INTO @TABLE (Yearmonth, Metric1)

    SELECT '2010/01/01', 1 UNION ALL

    SELECT '2010/02/01', 2 UNION ALL

    SELECT '2010/03/01', 3 UNION ALL

    SELECT '2010/04/01', 4 UNION ALL

    SELECT '2010/06/01', 5

    --Tally table would be better, for testing purposes I've included one here "on the fly"

    ;WITH t1 AS (SELECT 1 N UNION ALL SELECT 1 N),

    t2 AS (SELECT 1 N FROM t1 x, t1 y),

    t3 AS (SELECT 1 N FROM t2 x, t2 y),

    t4 AS (SELECT 1 N FROM t3 x, t3 y),

    tally AS (SELECT DATEADD(MONTH,ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0) AS thedate

    FROM t4 x, t4 y)

    --Actual code -> It's a god-awful mess, and could do with some optimising but gets the correct result

    UPDATE t2

    SET Metric2 = ISNULL(RunningTotal,0)

    FROM @TABLE t2

    LEFT OUTER JOIN (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) t1 ON t1.Yearmonth = t2.Yearmonth

    LEFT OUTER JOIN (SELECT Yearmonth, SUM(Metric1) AS RunningTotal

    FROM (SELECT A.Yearmonth AS Yearmonth, B.Yearmonth AS Yearmonth_B,

    B.Metric1

    FROM (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) A

    CROSS JOIN (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) B

    WHERE (B.ID >= A.ID-2 AND B.ID <= A.ID) ) T

    GROUP BY Yearmonth) tot ON t1.Yearmonth=tot.Yearmonth

    WHERE t1.Yearmonth = t2.Yearmonth

    SELECT * FROM @TABLE

    --EDIT--

    ChrisM@Work (8/31/2011)


    UPDATE #Sample SET Metric2 = ISNULL(NULLIF(Metric1+Metric1-1+Metric1-2,0),1)

    πŸ˜‰

    LOL!! Didn't think of that for the previous requirements. Awesome, will have to remember not to over-complicate things. . . *glances at new version of code*. . . hmmm :hehe:


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

  • Right, thanks for this, have submitted the code to 'extreme' stress testing and it's working well. I'll now integrate it and let you know. Thanks very much for you help, old chap.

  • If I wanted to sum up more than 1 metric in this way, how could this be done.

    I've tried to amend the code, but I'm not sure I understand it all and so it now contains syntax errors that I can't address. I wonder if the tally table needs extending ? Here's what I had so far though, wonder if you can tweak ?

    Regards, G

    --First, lets build some test data

    DECLARE @TABLE AS TABLE (Yearmonth DATETIME, Metric1 INT, Metric2 INT, Metric3 INT, Metric4 INT)

    INSERT INTO @TABLE (Yearmonth, Metric1, Metric3)

    SELECT '2010/02/01', 10, 100 UNION ALL

    SELECT '2010/06/01', 2, 200 UNION ALL

    SELECT '2010/03/01', 20, 2000 UNION ALL

    SELECT '2010/04/01', 4, 40 UNION ALL

    SELECT '2010/01/01', 30, 300

    --Tally table would be better, for testing purposes I've included one here "on the fly"

    ;

    WITH t1 AS (SELECT 1 N UNION ALL SELECT 1 N),

    t2 AS (SELECT 1 N FROM t1 x, t1 y),

    t3 AS (SELECT 1 N FROM t2 x, t2 y),

    t4 AS (SELECT 1 N FROM t3 x, t3 y),

    tally AS (SELECT DATEADD(MONTH,ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0) AS thedate

    FROM t4 x, t4 y)

    --Actual code -> It's a god-awful mess, and could do with some optimising but gets the correct result

    UPDATE t2

    SET Metric2 = ISNULL(RunningTotal,0), Metric4 = ISNULL(RunningTotal3,0)

    FROM @TABLE t2

    LEFT OUTER JOIN (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1, ISNULL(b.Metric3,0) as Metric3

    /**/

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) t1 ON t1.Yearmonth = t2.Yearmonth

    /**/

    LEFT OUTER JOIN (SELECT Yearmonth, SUM(Metric1) AS RunningTotal, SUM(Metric3) AS RunningTotal3

    FROM (SELECT A.Yearmonth AS Yearmonth, B.Yearmonth AS Yearmonth_B,

    B.Metric1, B.Metric3

    FROM (SELECT Yearmonth, Metric1, Metric3, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    /**/

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1, ISNULL(b.Metric3,0) as Metric3

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) A

    CROSS JOIN (SELECT Yearmonth, Metric1, Metric3 ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1 , ISNULL(b.Metric3,0) as Metric3

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) B

    WHERE (B.ID >= A.ID-2 AND B.ID <= A.ID) ) T

    GROUP BY Yearmonth) tot ON t1.Yearmonth=tot.Yearmonth

    WHERE t1.Yearmonth = t2.Yearmonth

    SELECT * FROM @TABLE

  • The tally table is fine, it gives dates between 1900-01-01 and 7361-04-01 so you're unlikely to have hit the ceiling there πŸ˜›

    Try this: -

    --First, lets build some test data

    DECLARE @TABLE AS TABLE (Yearmonth DATETIME, Metric1 INT, Metric2 INT, Metric3 INT, Metric4 INT)

    INSERT INTO @TABLE (Yearmonth, Metric1, Metric3)

    SELECT '2010/02/01', 10, 100 UNION ALL

    SELECT '2010/06/01', 2, 200 UNION ALL

    SELECT '2010/03/01', 20, 2000 UNION ALL

    SELECT '2010/04/01', 4, 40 UNION ALL

    SELECT '2010/01/01', 30, 300

    --Tally table would be better, for testing purposes I've included one here "on the fly"

    ;WITH t1 AS (SELECT 1 N UNION ALL SELECT 1 N),

    t2 AS (SELECT 1 N FROM t1 x, t1 y),

    t3 AS (SELECT 1 N FROM t2 x, t2 y),

    t4 AS (SELECT 1 N FROM t3 x, t3 y),

    tally AS (SELECT DATEADD(MONTH,ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0) AS thedate

    FROM t4 x, t4 y)

    --Actual code -> It's a god-awful mess, and could do with some optimising but gets the correct result

    UPDATE t2

    SET Metric2 = ISNULL(RunningTotal,0), Metric4 = ISNULL(RunningTotal2,0)

    FROM @TABLE t2

    LEFT OUTER JOIN (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1, ISNULL(b.Metric3,0) AS Metric3

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) AS minYearMonth, MAX(Yearmonth) AS maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) t1 ON t1.Yearmonth = t2.Yearmonth

    LEFT OUTER JOIN (SELECT Yearmonth, SUM(Metric1) AS RunningTotal, SUM(Metric3) AS RunningTotal2

    FROM (SELECT A.Yearmonth AS Yearmonth, B.Metric1, B.Metric3

    FROM (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) A

    CROSS JOIN (SELECT Yearmonth, Metric1, ROW_NUMBER() OVER(ORDER BY YearMonth) AS ID, Metric3

    FROM (SELECT a.Yearmonth, ISNULL(b.Metric1,0) AS Metric1, ISNULL(b.Metric3,0) AS Metric3

    FROM (SELECT thedate AS Yearmonth

    FROM (SELECT MIN(Yearmonth) minYearMonth, MAX(Yearmonth) maxYearMonth

    FROM @TABLE) a

    CROSS APPLY tally

    WHERE minYearMonth <= thedate AND maxYearMonth >= thedate) a

    LEFT OUTER JOIN @TABLE b ON a.Yearmonth = b.Yearmonth) a) B

    WHERE (B.ID >= A.ID-2 AND B.ID <= A.ID) ) T

    GROUP BY Yearmonth) tot ON t1.Yearmonth=tot.Yearmonth

    WHERE t1.Yearmonth = t2.Yearmonth

    SELECT * FROM @TABLE

    ORDER BY Yearmonth


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

  • OK, I see what's happening here a little more clearly, at least with the tally table.

    However, would you believe it (;-)) I am still a bit stuck, as I see there are in fact other non metric values in the mix that I need to group on. This means there'd be 1 row for each unique combination of location, grade and yearmonth.

    I made up some test data to illustrate. But I can't work out where to add the extra grouping additional and select columns - it's just too complex for me to really understand. Umm, I wonder if you are able to help ?

    DECLARE @TABLE AS TABLE (Location VARCHAR(50), Grade VARCHAR(50), Yearmonth DATETIME, Metric1 INT, Metric2 INT, Metric3 INT, Metric4 INT)

    INSERT INTO @TABLE (Location, Grade, Yearmonth, Metric1, Metric3)

    SELECT 'Small House', 'A', 2010/02/01', 10, 100 UNION ALL

    SELECT 'Big House', 'B','2010/06/01', 2, 200 UNION ALL

    SELECT 'Big House', 'A','2010/06/01', 20, 2000 UNION ALL

    SELECT 'Small House', 'A','2010/04/01', 4, 40 UNION ALL

    SELECT 'Small House', 'A','2010/01/01', 30, 300

    Regards, Greg.

  • Could you show me the expected output?


    Forever trying to learn
    My blog - http://www.cadavre.co.uk/
    For better, quicker answers on T-SQL questions, click on the following...http://www.sqlservercentral.com/articles/Best+Practices/61537/
    For better, quicker answers on SQL Server performance related questions, click on the following...http://www.sqlservercentral.com/articles/SQLServerCentral/66909/

Viewing 15 posts - 1 through 15 (of 24 total)

You must be logged in to reply to this topic. Login to reply