Running Multiplication in T-SQL

后端 未结 6 1161
無奈伤痛
無奈伤痛 2021-02-03 13:16

GTS Table

CCP months   QUART   YEARS  GTS
----  ------  -----    ----- ---
CCP1    1       1   2015    5
CCP1    2       1   2015    6
CCP1    3       1   2015         


        
6条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-02-03 14:05

    Another method that uses the EXP(SUM(LOG())) trick and only window functions for the running total (no recursive CTEs or cursors).

    Tested at dbfiddle.uk:

    WITH 
      ct AS
      ( SELECT  
            ccp, years, quart, 
            q2 = round(exp(coalesce(sum(log(sum(gts))) 
                                    OVER (PARTITION BY ccp 
                                          ORDER BY years, quart 
                                          ROWS BETWEEN UNBOUNDED PRECEDING 
                                                   AND 1 PRECEDING)
                                   , 0))
                      , 2)     -- round appropriately to your requirements
        FROM gts 
        GROUP BY ccp, years, quart
      )
    SELECT  
        g.*, 
        result = g.gts * b.baseline * ct.q2,
        baseline = b.baseline * ct.q2
    FROM ct 
      JOIN gts AS g
        ON  ct.ccp   = g.ccp
        AND ct.years = g.years 
        AND ct.quart = g.quart 
      CROSS APPLY
            ( SELECT TOP (1) b.baseline
              FROM baseline AS b
              WHERE b.ccp = ct.ccp
              ORDER BY b.years, b.quart
            ) AS b
      ;
    

    How it works:

    • (CREATE tables and INSERT skipped)

    • 1, lets group by ccp, year and quart and calculate the sums:

        select 
            ccp, years, quart,
            q1 = sum(gts)
        from gts 
        group by ccp, years, quart ;
    GO
    
    ccp  | years | quart | q1       
    :--- | ----: | ----: | :--------
    CCP1 |  2015 |     1 | 18.000000
    CCP1 |  2015 |     2 | 8.000000 
    CCP1 |  2015 |     3 | 6.000000 
    CCP1 |  2015 |     4 | 9.000000 
    CCP1 |  2016 |     1 | 12.000000
    
    • 2, we use the EXP(LOG(SUM()) trick to calculate the running multiplications of these sums. We use BETWEEEN .. AND -1 PRECEDING in the window to skip the current values, as these values are only used for the baselines of the next quart.
      The rounding is to avoid inaccuracies that come from using LOG() and EXP(). You can experiment with using either ROUND() or casting to NUMERIC:

    with 
      ct as
      ( select 
            ccp, years, quart,
            q1 = sum(gts)
        from gts 
        group by ccp, years, quart
      )
    select 
        ccp,  years, quart, -- months, gts, q1,
        q2 = round(exp(coalesce(sum(log(q1)) 
                                OVER (PARTITION BY ccp 
                                      ORDER BY Years, Quart 
                                      ROWS BETWEEN UNBOUNDED PRECEDING 
                                               AND 1 PRECEDING),0)),2)
    from ct ;
    GO
    
    ccp  | years | quart |   q2
    :--- | ----: | ----: | ---:
    CCP1 |  2015 |     1 |    1
    CCP1 |  2015 |     2 |   18
    CCP1 |  2015 |     3 |  144
    CCP1 |  2015 |     4 |  864
    CCP1 |  2016 |     1 | 7776
    
    • 3, we combine the two queries in one (no need for that, it just makes the query more compact, you could have 2 CTEs instead) and then join to gts so we can multiply each value with the calculated q2 (which gives us the baseline).
      The CROSS APPLY is merely to get the base baseline for each ccp.
      Note that I change this one slightly, to numeric(22,6) instead of rounding to 2 decimal places. The results are the same with the sample but they may differ if the numbers are bigger or not integer:

    with 
      ct as
      ( select 
            ccp, years, quart, 
            q2 = cast(exp(coalesce(sum(log(sum(gts)))
                                    OVER (PARTITION BY ccp 
                                          ORDER BY years, quart 
                                          ROWS BETWEEN UNBOUNDED PRECEDING 
                                                   AND 1 PRECEDING)
                                   , 0.0))
                       as numeric(22,6))           -- round appropriately to your requirements
        from gts 
        group by ccp, years, quart
      )
    select 
        g.*, 
        result = g.gts * b.baseline * ct.q2,
        baseline = b.baseline * ct.q2
    from ct 
      join gts as g
        on  ct.ccp   = g.ccp
        and ct.years = g.years 
        and ct.quart = g.quart 
      cross apply
            ( select top (1) baseline
              from baseline as b
              where b.ccp = ct.ccp
              order by years, quart
            ) as b
      ;
    GO
    
    CCP  | months | QUART | YEARS | GTS      | result        | baseline    
    :--- | -----: | ----: | ----: | :------- | :------------ | :-----------
    CCP1 |      1 |     1 |  2015 | 5.000000 | 25.000000     | 5.000000    
    CCP1 |      2 |     1 |  2015 | 6.000000 | 30.000000     | 5.000000    
    CCP1 |      3 |     1 |  2015 | 7.000000 | 35.000000     | 5.000000    
    CCP1 |      4 |     2 |  2015 | 4.000000 | 360.000000    | 90.000000   
    CCP1 |      5 |     2 |  2015 | 2.000000 | 180.000000    | 90.000000   
    CCP1 |      6 |     2 |  2015 | 2.000000 | 180.000000    | 90.000000   
    CCP1 |      7 |     3 |  2015 | 3.000000 | 2160.000000   | 720.000000  
    CCP1 |      8 |     3 |  2015 | 2.000000 | 1440.000000   | 720.000000  
    CCP1 |      9 |     3 |  2015 | 1.000000 | 720.000000    | 720.000000  
    CCP1 |     10 |     4 |  2015 | 2.000000 | 8640.000000   | 4320.000000 
    CCP1 |     11 |     4 |  2015 | 3.000000 | 12960.000000  | 4320.000000 
    CCP1 |     12 |     4 |  2015 | 4.000000 | 17280.000000  | 4320.000000 
    CCP1 |      1 |     1 |  2016 | 8.000000 | 311040.000000 | 38880.000000
    CCP1 |      2 |     1 |  2016 | 1.000000 | 38880.000000  | 38880.000000
    CCP1 |      3 |     1 |  2016 | 3.000000 | 116640.000000 | 38880.000000
    

提交回复
热议问题