Running Total Performance

  • Kathi Kellenberger (4/4/2015)


    Jeff -- For the running totals functionality, I think the performance is pretty good if you use ROWS and have the correct index in place. Where I really wish they did things better is windows aggregates without the ORDER BY in the OVER clause.

    It's not bad according to most people's standards and if you're doing the whole table, you don't actually need an index but it's still more than 7 times slower than it should have been, IMHO. See the following test results from an early test.

    http://blog.waynesheffield.com/wayne/archive/2011/08/running-totals-in-denali-ctp3/

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • I'm not an expert on the Quirky Update, but don't you have to run the update which reads the entire table and then select the data after that which also reads the table?

    Generally, the running total with ROWS will read about the same number of pages as selecting the same data without the calculation. Looks like the Quirky Update is better if you want to do an actual update, but if you just want to select the data, using the windows running total with ROWS is better.

    Aunt Kathi Data Platform MVP
    Author of Expert T-SQL Window Functions
    Simple-Talk Editor

  • Kathi Kellenberger (4/4/2015)


    Jeff -- For the running totals functionality, I think the performance is pretty good if you use ROWS and have the correct index in place. Where I really wish they did things better is windows aggregates without the ORDER BY in the OVER clause.

    I have been looking into few of the running totals tests lately and found many of them quite flawed, including Wayne's, here are two results using his test data set with a slight modifications to the harness. Difference is more like 2x rather than 7x.

    😎

    WINDOW FUNCTION ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ------------------------------------------

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'TransactionDetail'. Scan count 1, logical reads 6085, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times: CPU time = 1248 ms, elapsed time = 1245 ms.

    QUIRKY UPDATE------------------------------------------

    Table 'TransactionDetail'. Scan count 1, logical reads 6085, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times: CPU time = 639 ms, elapsed time = 645 ms.

    SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.

  • Kathi Kellenberger (4/4/2015)


    I'm not an expert on the Quirky Update, but don't you have to run the update which reads the entire table and then select the data after that which also reads the table?

    Generally, the running total with ROWS will read about the same number of pages as selecting the same data without the calculation. Looks like the Quirky Update is better if you want to do an actual update, but if you just want to select the data, using the windows running total with ROWS is better.

    My findings are that the Quirky Updates are still around 2x faster although the cardinality and the distribution of the set does have quite an impact on the performance.

    😎

  • In your test, are you just performing the update or are you also selecting the data?

    Aunt Kathi Data Platform MVP
    Author of Expert T-SQL Window Functions
    Simple-Talk Editor

  • Kathi Kellenberger (4/4/2015)


    In your test, are you just performing the update or are you also selecting the data?

    I'm selecting the data into "bucket" variables

    😎

    Edit: typo^2

  • Eirikur Eiriksson (4/4/2015)


    Kathi Kellenberger (4/4/2015)


    Jeff -- For the running totals functionality, I think the performance is pretty good if you use ROWS and have the correct index in place. Where I really wish they did things better is windows aggregates without the ORDER BY in the OVER clause.

    I have been looking into few of the running totals tests lately and found many of them quite flawed, including Wayne's, here are two results using his test data set with a slight modifications to the harness. Difference is more like 2x rather than 7x.

    😎

    WINDOW FUNCTION ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ------------------------------------------

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'TransactionDetail'. Scan count 1, logical reads 6085, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times: CPU time = 1248 ms, elapsed time = 1245 ms.

    QUIRKY UPDATE------------------------------------------

    Table 'TransactionDetail'. Scan count 1, logical reads 6085, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times: CPU time = 639 ms, elapsed time = 645 ms.

    SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.

    Got code and test data for that, Eirikur? And what is wrong with Wayne's test harness? Please be sure to include yours.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • patricklambin (4/3/2015)


    Interesting question but maybe too easy with some minutes to spend :

    - Plenty of RAM : too easy to be the good choice

    - to use OPTIMIZE : never seen this parameter in the OVER clause

    - to use older methods : surprising

    only one left choice

    Good analysis Patrick 🙂

    Thanks & Best Regards,
    Hany Helmy
    SQL Server Database Consultant

  • Hi Hany ,

    Thank you for your comment.

    My own full comment about the older method should be ended by : why not if they can be applied without doing the reading of the query ?

    But it is only today I found a not too bad formulation about my thoughts ( always this dreadful problem to translate from French towards English ... )

  • Jeff Moden (4/4/2015)


    Got code and test data for that, Eirikur? And what is wrong with Wayne's test harness? Please be sure to include yours.

    Thought you would be interested;-)

    Still a work in progress, busy chasing Easter/Dust bunnies these days, will post more complete testing and finding as soon as I have time. In the meantime, here is a partial testing code which strongly indicates that the original testing is somewhat flawed.

    😎

    Quick note, moved the testing from tempdb into a dedicated Test database for more realistic results.

    The first part times each operation including the reset update of the result table, the second runs the same test with STATISTICS IO.

    USE Test;

    GO

    SET NOCOUNT ON;

    GO

    /*************************************************************************************

    Timer table

    *************************************************************************************/

    IF OBJECT_ID(N'dbo.TBL_TIMER') IS NOT NULL DROP TABLE dbo.TBL_TIMER;

    CREATE TABLE dbo.TBL_TIMER

    (

    TT_TEXT VARCHAR(200) NOT NULL

    ,TT_TIME DATETIME2(7) NOT NULL CONSTRAINT DFLT_DBO_TT_TIMER_TT_TIME DEFAULT (SYSDATETIME())

    );

    GO

    DECLARE @INT_BUCKET_01 INT = 0;

    DECLARE @DATE_BUCKET DATETIME = NULL;

    DECLARE @INT_BUCKET_02 INT = 0;

    DECLARE @MONEY_BUCKET_01 MONEY = 0;

    DECLARE @INT_BUCKET_03 INT = 0;

    DECLARE @MONEY_BUCKET_02 MONEY = 0;

    DECLARE @INT_BUCKET_04 INT = 0;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 1');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 1');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 UPDATE');

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    UPDATE TD

    SET TD.AccountRunningTotal = AD.AccountRunningTotal

    ,TD.AccountRunningCount = AD.AccountRunningCount

    FROM dbo.TransactionDetail TD

    INNER JOIN AGGREGATED_DATA AD

    ON TD.TransactionDetailID = AD.TransactionDetailID;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 UPDATE');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 2');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 2');

    IF OBJECT_ID(N'dbo.TBL_TMP_RESULT') IS NOT NULL DROP TABLE dbo.TBL_TMP_RESULT;

    CREATE TABLE dbo.TBL_TMP_RESULT

    (

    TransactionDetailID INT NOT NULL PRIMARY KEY CLUSTERED

    ,AccountRunningTotal MONEY NOT NULL

    ,AccountRunningCount INT NOT NULL

    );

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 UPDATE 2');

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    INSERT INTO dbo.TBL_TMP_RESULT WITH (TABLOCK) (TransactionDetailID,AccountRunningTotal,AccountRunningCount)

    SELECT

    AD.TransactionDetailID

    ,AD.AccountRunningTotal

    ,AD.AccountRunningCount

    FROM AGGREGATED_DATA AD

    UPDATE TD WITH (TABLOCK)

    SET TD.AccountRunningTotal = AD.AccountRunningTotal

    ,TD.AccountRunningCount = AD.AccountRunningCount

    FROM dbo.TransactionDetail TD

    INNER JOIN dbo.TBL_TMP_RESULT AD

    ON TD.TransactionDetailID = AD.TransactionDetailID;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 UPDATE 2');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 3');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 3');

    IF OBJECT_ID(N'dbo.TBL_TMP_RESULT') IS NOT NULL DROP TABLE dbo.TBL_TMP_RESULT;

    CREATE TABLE dbo.TBL_TMP_RESULT

    (

    TransactionDetailID INT NOT NULL PRIMARY KEY CLUSTERED

    ,AccountRunningTotal MONEY NOT NULL

    ,AccountRunningCount INT NOT NULL

    );

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 INSERT SELECT');

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    INSERT INTO dbo.TBL_TMP_RESULT WITH (TABLOCK) (TransactionDetailID,AccountRunningTotal,AccountRunningCount)

    SELECT

    AD.TransactionDetailID

    ,AD.AccountRunningTotal

    ,AD.AccountRunningCount

    FROM AGGREGATED_DATA AD;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 INSERT SELECT');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 4');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 4');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 SELECT');

    SELECT

    @INT_BUCKET_01 = TD.TransactionDetailID

    ,@DATE_BUCKET = TD.Date

    ,@INT_BUCKET_02 = TD.AccountID

    ,@MONEY_BUCKET_01 = TD.Amount

    ,@INT_BUCKET_03 = TD.NCID

    ,@MONEY_BUCKET_02 = SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) --AS AccountRunningTotal

    ,@INT_BUCKET_04 = COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) --AS AccountRunningCount

    FROM dbo.TransactionDetail TD;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('METHOD 1 SELECT');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 5');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 5');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW');

    ;WITH cte AS

    (

    SELECT AccountRunningTotal,

    AccountRunningCount,

    ART = SUM(Amount) OVER (PARTITION BY AccountID

    ORDER BY Date, TransactionDetailID

    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),

    ARC = SUM(1) OVER (PARTITION BY AccountID

    ORDER BY Date, TransactionDetailID

    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

    FROM dbo.TransactionDetail

    )

    UPDATE cte

    SET AccountRunningTotal = ART,

    AccountRunningCount = ARC

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 6');

    EXEC dbo.ResetTestTable;

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('EXEC dbo.ResetTestTable 6');

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('Quirky Update running totals');

    -- Quirky Update running totals

    --===== Declare the working variables

    DECLARE @PrevAccountID INT

    DECLARE @AccountRunningTotal MONEY

    DECLARE @AccountRunningCount INT

    --===== Update the running total and running count for this row using the "Quirky

    -- Update" and a "Pseudo-cursor". The order of the UPDATE is controlled by the

    -- order of the clustered index.

    UPDATE dbo.TransactionDetail

    SET @AccountRunningTotal = AccountRunningTotal = CASE

    WHEN AccountID = @PrevAccountID

    THEN @AccountRunningTotal + Amount

    ELSE Amount

    END,

    @AccountRunningCount = AccountRunningCount = CASE

    WHEN AccountID = @PrevAccountID

    THEN @AccountRunningCount + 1

    ELSE 1

    END,

    @PrevAccountID = AccountID

    FROM dbo.TransactionDetail WITH (TABLOCKX)

    OPTION (MAXDOP 1);

    INSERT INTO dbo.TBL_TIMER(TT_TEXT) VALUES('Quirky Update running totals');

    SELECT

    T.TT_TEXT

    ,DATEDIFF(MICROSECOND,MIN(T.TT_TIME),MAX(T.TT_TIME)) AS DURATION

    FROM dbo.TBL_TIMER T

    GROUP BY T.TT_TEXT

    ORDER BY DURATION;

    Timer Results

    TT_TEXT DURATION

    ------------------------------------------------------------------------------------ -----------

    EXEC dbo.ResetTestTable 4 770044

    EXEC dbo.ResetTestTable 5 776044

    EXEC dbo.ResetTestTable 1 1201069

    EXEC dbo.ResetTestTable 2 1274073

    EXEC dbo.ResetTestTable 6 1282073

    EXEC dbo.ResetTestTable 3 1291073

    Quirky Update running totals 1572090

    METHOD 1 SELECT 1736099

    METHOD 1 INSERT SELECT 2648151

    METHOD 1 UPDATE 7251415

    METHOD 1 UPDATE 2 8545489

    DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 13900795

    USE Test;

    GO

    SET NOCOUNT ON;

    GO

    GO

    DECLARE @INT_BUCKET_01 INT = 0;

    DECLARE @DATE_BUCKET DATETIME = NULL;

    DECLARE @INT_BUCKET_02 INT = 0;

    DECLARE @MONEY_BUCKET_01 MONEY = 0;

    DECLARE @INT_BUCKET_03 INT = 0;

    DECLARE @MONEY_BUCKET_02 MONEY = 0;

    DECLARE @INT_BUCKET_04 INT = 0;

    EXEC dbo.ResetTestTable;

    RAISERROR(N'METHOD 1 UPDATE -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    UPDATE TD

    SET TD.AccountRunningTotal = AD.AccountRunningTotal

    ,TD.AccountRunningCount = AD.AccountRunningCount

    FROM dbo.TransactionDetail TD

    INNER JOIN AGGREGATED_DATA AD

    ON TD.TransactionDetailID = AD.TransactionDetailID;

    SET STATISTICS IO OFF;

    EXEC dbo.ResetTestTable;

    IF OBJECT_ID(N'dbo.TBL_TMP_RESULT') IS NOT NULL DROP TABLE dbo.TBL_TMP_RESULT;

    CREATE TABLE dbo.TBL_TMP_RESULT

    (

    TransactionDetailID INT NOT NULL PRIMARY KEY CLUSTERED

    ,AccountRunningTotal MONEY NOT NULL

    ,AccountRunningCount INT NOT NULL

    );

    RAISERROR(N'METHOD 1 UPDATE 2 -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    INSERT INTO dbo.TBL_TMP_RESULT WITH (TABLOCK) (TransactionDetailID,AccountRunningTotal,AccountRunningCount)

    SELECT

    AD.TransactionDetailID

    ,AD.AccountRunningTotal

    ,AD.AccountRunningCount

    FROM AGGREGATED_DATA AD

    UPDATE TD WITH (TABLOCK)

    SET TD.AccountRunningTotal = AD.AccountRunningTotal

    ,TD.AccountRunningCount = AD.AccountRunningCount

    FROM dbo.TransactionDetail TD

    INNER JOIN dbo.TBL_TMP_RESULT AD

    ON TD.TransactionDetailID = AD.TransactionDetailID;

    SET STATISTICS IO OFF;

    EXEC dbo.ResetTestTable;

    IF OBJECT_ID(N'dbo.TBL_TMP_RESULT') IS NOT NULL DROP TABLE dbo.TBL_TMP_RESULT;

    CREATE TABLE dbo.TBL_TMP_RESULT

    (

    TransactionDetailID INT NOT NULL PRIMARY KEY CLUSTERED

    ,AccountRunningTotal MONEY NOT NULL

    ,AccountRunningCount INT NOT NULL

    );

    RAISERROR(N'METHOD 1 INSERT SELECT -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    ;WITH AGGREGATED_DATA AS

    (

    SELECT

    TD.TransactionDetailID

    ,SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningTotal

    ,COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) AS AccountRunningCount

    FROM dbo.TransactionDetail TD

    )

    INSERT INTO dbo.TBL_TMP_RESULT WITH (TABLOCK) (TransactionDetailID,AccountRunningTotal,AccountRunningCount)

    SELECT

    AD.TransactionDetailID

    ,AD.AccountRunningTotal

    ,AD.AccountRunningCount

    FROM AGGREGATED_DATA AD;

    SET STATISTICS IO OFF;

    EXEC dbo.ResetTestTable;

    RAISERROR(N'METHOD 1 SELECT -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    SELECT

    @INT_BUCKET_01 = TD.TransactionDetailID

    ,@DATE_BUCKET = TD.Date

    ,@INT_BUCKET_02 = TD.AccountID

    ,@MONEY_BUCKET_01 = TD.Amount

    ,@INT_BUCKET_03 = TD.NCID

    ,@MONEY_BUCKET_02 = SUM(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) --AS AccountRunningTotal

    ,@INT_BUCKET_04 = COUNT(TD.Amount) OVER

    (

    PARTITION BY TD.AccountID

    ORDER BY TD.Date

    ROWS BETWEEN UNBOUNDED PRECEDING

    AND CURRENT ROW

    ) --AS AccountRunningCount

    FROM dbo.TransactionDetail TD;

    SET STATISTICS IO OFF;

    EXEC dbo.ResetTestTable;

    -- DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

    RAISERROR(N'DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    ;WITH cte AS

    (

    SELECT AccountRunningTotal,

    AccountRunningCount,

    ART = SUM(Amount) OVER (PARTITION BY AccountID

    ORDER BY Date, TransactionDetailID

    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),

    ARC = SUM(1) OVER (PARTITION BY AccountID

    ORDER BY Date, TransactionDetailID

    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

    FROM dbo.TransactionDetail

    )

    UPDATE cte

    SET AccountRunningTotal = ART,

    AccountRunningCount = ARC

    SET STATISTICS IO OFF;

    EXEC dbo.ResetTestTable;

    RAISERROR(N'Quirky Update running totals -------------------------------------',0,0) WITH NOWAIT;

    SET STATISTICS IO ON;

    -- Quirky Update running totals

    --===== Declare the working variables

    DECLARE @PrevAccountID INT

    DECLARE @AccountRunningTotal MONEY

    DECLARE @AccountRunningCount INT

    --===== Update the running total and running count for this row using the "Quirky

    -- Update" and a "Pseudo-cursor". The order of the UPDATE is controlled by the

    -- order of the clustered index.

    UPDATE dbo.TransactionDetail

    SET @AccountRunningTotal = AccountRunningTotal = CASE

    WHEN AccountID = @PrevAccountID

    THEN @AccountRunningTotal + Amount

    ELSE Amount

    END,

    @AccountRunningCount = AccountRunningCount = CASE

    WHEN AccountID = @PrevAccountID

    THEN @AccountRunningCount + 1

    ELSE 1

    END,

    @PrevAccountID = AccountID

    FROM dbo.TransactionDetail WITH (TABLOCKX)

    OPTION (MAXDOP 1);

    SET STATISTICS IO OFF;

    IO Statistics

    METHOD 1 UPDATE -------------------------------------

    Table 'TransactionDetail'. Scan count 10, logical reads 3011623, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    METHOD 1 UPDATE 2 -------------------------------------

    Table 'TransactionDetail'. Scan count 5, logical reads 8852, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'TBL_TMP_RESULT'. Scan count 5, logical reads 3121, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'TransactionDetail'. Scan count 5, logical reads 3065272, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    METHOD 1 INSERT SELECT -------------------------------------

    Table 'TransactionDetail'. Scan count 5, logical reads 8844, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    METHOD 1 SELECT -------------------------------------

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'TransactionDetail'. Scan count 1, logical reads 8722, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    DENALI CTP3 Running Totals using ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -------------------------------------

    Table 'TransactionDetail'. Scan count 1, logical reads 5956654, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Quirky Update running totals -------------------------------------

    Table 'TransactionDetail'. Scan count 1, logical reads 8722, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

  • Eirikur Eiriksson (4/5/2015)


    here is a partial testing code which strongly indicates that the original testing is somewhat flawed.

    Sorry... I don't have the time to wade through a ton of undocumented code. What indication and how so?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (4/5/2015)


    Eirikur Eiriksson (4/5/2015)


    here is a partial testing code which strongly indicates that the original testing is somewhat flawed.

    Sorry... I don't have the time to wade through a ton of undocumented code. What indication and how so?

    No worries, I didn't have time to comment/document either. The main flaw is the excessive read in the CTE update implementation in the original test harness, even by doing an insert into a table and join update just matches the number of reads. A different kind of update with CTE as a source has half the number of reads compared to the original. There is more and I will elaborate on this as soon as I have time.

    😎

  • Eirikur Eiriksson (4/5/2015)


    Jeff Moden (4/5/2015)


    Eirikur Eiriksson (4/5/2015)


    here is a partial testing code which strongly indicates that the original testing is somewhat flawed.

    Sorry... I don't have the time to wade through a ton of undocumented code. What indication and how so?

    No worries, I didn't have time to comment/document either. The main flaw is the excessive read in the CTE update implementation in the original test harness, even by doing an insert into a table and join update just matches the number of reads. A different kind of update with CTE as a source has half the number of reads compared to the original. There is more and I will elaborate on this as soon as I have time.

    😎

    Looking forward to it. Thanks, Eirikur.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (4/5/2015)


    Eirikur Eiriksson (4/5/2015)


    Jeff Moden (4/5/2015)


    Eirikur Eiriksson (4/5/2015)


    here is a partial testing code which strongly indicates that the original testing is somewhat flawed.

    Sorry... I don't have the time to wade through a ton of undocumented code. What indication and how so?

    No worries, I didn't have time to comment/document either. The main flaw is the excessive read in the CTE update implementation in the original test harness, even by doing an insert into a table and join update just matches the number of reads. A different kind of update with CTE as a source has half the number of reads compared to the original. There is more and I will elaborate on this as soon as I have time.

    😎

    Looking forward to it. Thanks, Eirikur.

    Me too. Some of these performance comparisons are my favorite threads. 😉

  • Thanks for the question.

Viewing 15 posts - 16 through 30 (of 30 total)

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