How to calculate Running Multiplication

后端 未结 4 1642
逝去的感伤
逝去的感伤 2020-12-16 18:11

I have two tables

WAC table

ID  wac_inc             item
--  -----------------   ----
1   2.310000000000000   A
2   1.10000000000000         


        
相关标签:
4条回答
  • 2020-12-16 18:41

    Try:

    DECLARE @t TABLE
        (
          ID INT ,
          wac DECIMAL(30, 10) ,
          item CHAR(1)
        )
    DECLARE @b TABLE
        (
          item CHAR(1) ,
          baseline DECIMAL(30, 10)
        )
    
    INSERT  INTO @t
    VALUES  ( 1, 2.31, 'A' ),
            ( 2, 1.10, 'A' ),
            ( 3, 2.13, 'A' ),
            ( 4, 1.34, 'A' )
    
    
    INSERT  INTO @b
    VALUES  ( 'A', 10 );
    
    
    WITH    ordercte
              AS ( SELECT   * ,
                            ROW_NUMBER() OVER ( PARTITION BY item ORDER BY ID ) AS rn
                   FROM     @t
                 ),
            rec
              AS ( SELECT   t.item ,
                            t.ID ,
                            t.wac ,
                            t.rn ,
                            b.baseline * ( 1 + ( t.wac / 100 ) ) AS m
                   FROM     ordercte t
                            JOIN @b b ON b.item = t.item
                   WHERE    t.rn = 1
                   UNION ALL
                   SELECT   t.item ,
                            t.ID ,
                            t.wac ,
                            t.rn ,
                            c.m * ( 1 + ( t.wac / 100 ) )
                   FROM     ordercte t
                            JOIN rec c ON t.item = c.item
                                          AND t.rn = c.rn + 1
                 )
        SELECT  id ,
                wac ,
                item ,
                m
        FROM    rec
    

    Output:

    id  wac             item    m
    1   2.3100000000    A       10.231000
    2   1.1000000000    A       10.343541
    3   2.1300000000    A       10.563858
    4   1.3400000000    A       10.705414
    

    EDIT1

    I was trying to implement LOG EXP trick but could not manage unless @usr lead me to solution. So all credits to user @usr:

    WITH    ordercte
              AS ( SELECT   t.ID ,
                            t.wac ,
                            t.item ,
                            b.baseline ,
                            ROW_NUMBER() OVER ( PARTITION BY t.item ORDER BY ID ) AS rn
                   FROM     @t t
                            JOIN @b b ON b.item = t.item
                 )
        SELECT  baseline
                * EXP(SUM(LOG(( 1 + ( wac / 100 ) ))) OVER ( PARTITION BY item ORDER BY rn )) AS m
        FROM    ordercte
    

    Or just:

    SELECT  t.ID, t.wac, t.item, baseline
            * EXP(SUM(LOG(( 1 + ( wac / 100 ) ))) OVER ( PARTITION BY t.item ORDER BY t.ID )) AS m
    FROM    @t t
            JOIN @b b ON b.item = t.item  
    

    if ID is the field you order by.

    Output:

    ID  wac             item    m
    1   2.3100000000    A       10.231
    2   1.1000000000    A       10.343541
    3   2.1300000000    A       10.5638584233
    4   1.3400000000    A       10.7054141261722
    

    EDIT2

    For SQL 2008 use:

    WITH    cte
              AS ( SELECT   t.ID ,
                            t.wac ,
                            t.item ,
                            baseline ,
                            ( SELECT    SUM(LOG(( 1 + ( wac / 100 ) )))
                              FROM      @t it
                              WHERE     it.item = t.item AND it.ID <= t.ID
                            ) AS e
                   FROM     @t t
                            JOIN @b b ON b.item = t.item
                 )
        SELECT  ID, wac, item, baseline * EXP(e) AS m
        FROM    cte
    

    EDIT3

    Here is complete solution for SQL Server 2008 with dialing with NULLs and negative values:

    WITH    cte
              AS ( SELECT   t.ID ,
                            t.wac ,
                            t.item ,
                            b.baseline , 
                            ca.e,
                            ca.n,
                            ca.m
                   FROM     @t t
                   JOIN @b b ON b.item = t.item
                   CROSS APPLY(SELECT   SUM(LOG(ABS(NULLIF( 1 +  wac / 100 , 0)))) as e,
                                        SUM(SIGN(CASE WHEN 1 +  wac / 100 < 0 THEN 1 ELSE 0 END)) AS n,
                                        MIN(ABS(1 +  wac / 100)) AS m
                              FROM      @t it
                              WHERE     it.item = t.item AND it.ID <= t.ID
                              ) ca
                 )
        SELECT  ID, wac, item, baseline *
                            CASE
                                WHEN m = 0 THEN 0
                                WHEN n % 2 = 1 THEN -1 * EXP(e)
                                ELSE EXP(e) 
                            END as Result
        FROM    cte
    
    0 讨论(0)
  • 2020-12-16 18:41

    You can transform a series of multiplications into a series of additions with the following math trick:

    exp(log(a) + log(b)) = a * b
    

    So MUL(a) is EXP(SUM(LOG(a))).

    SELECT SUM(val) AS [Sum], EXP(SUM(LOG(val))) AS Product
    FROM (VALUES 
        (1), (2), (3), (4)
    ) x(val)
    

    This emits sum = 10, product = 24.

    Potential problems are rounding errors and zero factors.

    You can now use one of the usual ways to achieve a running aggregate such as windowing functions. That's a solved problem.

    0 讨论(0)
  • 2020-12-16 18:46

    you could do it easily with a recursive cte:

    with rec(id ,wi,i,r) as 
    (
      select top (1) w.ID,w.wac_inc,w.item, b.baseline * (1 + (w.wac_inc/100))
      from wac w join baseline b on w.item=b.item
      union all
      select w.ID,w.wac_inc,w.item, r.r * (1 + (w.wac_inc/100))
      from wac w
      join rec r on (w.ID)-1 = r.id
     )
     select * from rec
    

    Output:

    1   2.31    A   10.231
    2   1.1     A   10.343541
    3   2.13    A   10.563858
    4   1.34    A   10.705414
    

    check in the demo


    EDIT - Adding another solution:

    you can do it by taking help from a copy of your original table :

    Assuming your schema and data is:

    create  table wac
      (ID int,wac_inc numeric(38,15),item char )
    
    insert wac
    values (1,2.31,'A'),
    (2,1.1,'A'),
    (3,2.13,'A'),
    (4,1.34,'A')
    

    1.take a copy from original table(use a temp table or a table variable) and update the first record from baseline table:

    create table  #tmp (ID int,wac_inc numeric(38,15),item char, Running_Mul numeric(38,15))
    insert into #tmp select id,wac_inc,item,null from wac
    
    update #tmp set Running_Mul = (select top 1 baseline from baseline)*(1+(wac_inc/100))
    where id = (select min(id) from #tmp)
    

    2.declare these variables:

    declare @id int,@rm numeric(38,15)
    select @id=min(id) from #tmp
    
    select @rm=Running_Mul from #tmp where id=@id
    

    3.update the copy:

    update #tmp
    set @rm=Running_Mul= case 
                        when @id <> id then @rm*(1+(wac_inc/100))
                        else Running_Mul
                     end,
    @id=id
    

    and now you can check the result:

    select * from #tmp
    drop table #tmp
    

    Result:

    ID  wac_inc             item    Running_Mul
    1   2.310000000000000   A   10.231000000000000
    2   1.100000000000000   A   10.343541000000000
    3   2.130000000000000   A   10.563858000000000
    4   1.340000000000000   A   10.705414000000000
    
    0 讨论(0)
  • 2020-12-16 18:49

    For the sake of completeness here is a full solution for SQL Server 2012 that uses the EXP(SUM(LOG(val))) trick suggested by @usr in another answer.

    WITH
    CTE
    AS
    (
        SELECT
            0 AS ID
            ,item
            ,baseline AS wac_inc
            ,baseline AS m
        FROM baseline
    
        UNION ALL
    
        SELECT
            ID
            ,item
            ,wac_inc
            ,1 + wac_inc/100 AS m
        FROM wac
    )
    SELECT
        ID
        ,item
        ,wac_inc
        ,m
        ,EXP(SUM(LOG(m)) OVER (PARTITION BY item ORDER BY ID ROWS UNBOUNDED PRECEDING)) AS MulRows
    FROM CTE;
    

    result set

    ID  item wac_inc            m                   MulRows
    0   A   10.000000000000000  10.000000000000000  10
    1   A   2.310000000000000   1.023100000000000   10.231
    2   A   1.100000000000000   1.011000000000000   10.343541
    3   A   2.130000000000000   1.021300000000000   10.5638584233
    4   A   1.340000000000000   1.013400000000000   10.7054141261722
    

    If SQL Server 2012 is available, this window SUM is very efficient. For previous versions any set-based solution would result in O(n*n) complexity, which means that cursor would be a better way. Here is a very good article by Aaron Bertrand comparing different methods of calculating running totals: http://sqlperformance.com/2012/07/t-sql-queries/running-totals Or SO question: Calculate running total / running balance

    Of course, if your table is small, then set-based solution with O(n*n) complexity may run faster than O(n) solution with the cursor due to cursor overhead, so you need to check the performance with your real data.

    0 讨论(0)
提交回复
热议问题