sql-servert-sqlsql-server-2008-r2cumulative-sumdbmail

SQL Server running total with dbmail when total met or exceeded


I am able to get a running total via script--but what I can't seem to do is isolate the line where I have met or exceeded a certain value...

SELECT 
    column1, 
    (SELECT SUM(column1) FROM table WHERE column2 <= t1.column2)
FROM 
    table  t1 

Ultimately what I would want to do is create a trigger to send dbmail when the Sum of column1 meets or exceeds (n)...help me obi-wan


Solution

  • On SQL-Server 2008 you can use next solution:

    DECLARE @TBL TABLE(id int, amount int);
    INSERT INTO @TBL VALUES
    (1, 100), (2, 100), (3, 60), (4, 200), (5, 100);
    
    SELECT     t1.ID, t1.amount, SUM(t2.amount) as CumTotal
    FROM       @TBL t1
    CROSS APPLY (SELECT *
           FROM @TBL
           WHERE ID <= t1.id) t2
    GROUP BY   t1.ID, t1.amount
    HAVING     SUM(t1.amount) < 300
    ORDER BY   t1.ID
    ;
    

    This is the result:

    ID | amount | CumTotal
    -: | -----: | -------:
     1 |    100 |      100
     2 |    100 |      200
     3 |     60 |      260
    

    dbfiddle here

    Although this solution works well, it is not recommendable on terms of performance.

    In this case the best option is to use a CURSOR.

    DECLARE @CS table (id int, amount int, total int);
    DECLARE @id int, @amount int;
    DECLARE @CumSum int = 0;
    DECLARE c CURSOR
        LOCAL STATIC FORWARD_ONLY READ_ONLY
        FOR SELECT ID, amount
            FROM @TBL
            ORDER BY [id];
     
    OPEN c;
    FETCH NEXT FROM c INTO @id, @amount
     
    WHILE @@FETCH_STATUS = 0 AND @CumSum + @amount < 300 
    BEGIN
        SET @CumSum = @CumSum + @amount;
        INSERT @CS (id, amount,  total)
        SELECT @id, @amount, @CumSum;
        FETCH NEXT FROM c INTO @id, @amount
    END
    
    CLOSE c;
    DEALLOCATE c;
     
    SELECT   id, amount, total
    FROM     @CS
    ORDER BY id;
    

    GO

    id | amount | total
    -: | -----: | ----:
     1 |    100 |   100
     2 |    100 |   200
     3 |     60 |   260
    

    dbfiddle here

    Next answer can be used on SQL-SERVER 2012 and above

    You can use a cumulative sum using a WINDOW function and SUM() ROWS UNBOUNDED PRECEDING.

    Have a look at MS docs.

    DECLARE @TBL TABLE(id int, amount int);
    INSERT INTO @TBL VALUES
    (1, 100), (2, 100), (3, 60), (4, 200), (5, 100);
    

    Next query returns a cumulative sum:

    SELECT ID, 
           SUM(amount) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING) AS CumTotal
    FROM   @TBL
    ;
    
    ID | CumTotal
    -: | -------:
     1 |      100
     2 |      200
     3 |      260
     4 |      460
     5 |      560
    

    The problem is that you cannot stop it, you need to calculate all records, and then you can apply a where clause just to filter the records.

    WITH CSum As
    (
        SELECT ID, 
               SUM(amount) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING) AS CumTotal
        FROM   @TBL
    )
    SELECT   ID, CumTotal
    FROM     CSum
    WHERE    CumTotal < 300
    ORDER BY ID
    ;
    

    This is the final result:

     
    ID | CumTotal
    -: | -------:
     1 |      100
     2 |      200
     3 |      260
    

    dbfiddle here