2015-04-07 35 views
11

Ho due tabelleCome calcolare esecuzione Moltiplicazione

tavolo

WAC

ID wac_inc    item 
-- ----------------- ---- 
1 2.310000000000000 A 
2 1.100000000000000 A 
3 2.130000000000000 A 
4 1.340000000000000 A 

Tabella Baseline

item baseline 
---- ------------------ 
A  10.000000000000000 

Risultato previsto

ID wac_inc    item Running_Mul 
-- ----------------- ---- ----------- 
1 2.310000000000000 A  10.231  -- 10 * (1+(2.310000000000000/100)) 
2 1.100000000000000 A  10.343541 -- 10.231 * (1+(1.100000000000000/100)) 
3 2.130000000000000 A  10.563858 -- 10.343541 * (1+(2.130000000000000/100)) 
4 1.340000000000000 A  10.705413 -- 10.563858 * (1+(1.340000000000000/100)) 

Formula trovare running_mul è

Baseline * (1 + (wac_inc/100))

SQLFIDDLE

qui per ogni riga precedente valore di riga Running_Mul è il baseline e per la prima riga baseline arriverà da baseline table.

Spero di averlo chiarito. AFAIK possiamo farlo usando CURSOR ma voglio evitareRBAR il più possibile. Qualcuno può suggerirmi il modo migliore di farlo.

+0

È possibile eseguire il logaritmo e calcolare una somma parziale poiché exp (log (a) + log (b)) = a * b'. Non sono sicuro della precisione numerica che conserva. – usr

+0

@usr - puoi condividere un esempio –

+0

Penso che sarebbe 'exp (sum (log (x)))'. – usr

risposta

8

Prova:

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 

uscita:

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

stavo cercando di implementare LOG EXP trucco, ma non riuscivo a meno @usr mi portano a soluzione. Quindi tutti i crediti a @usr utente:

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 

O solo:

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 

se ID è il campo si ordina per.

uscita:

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

Per SQL 2008 Usa:

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

Ecco soluzione completa per SQL Servire r 2008 con la composizione con i NULL e valori negativi:

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

Eccellente amico. Ma sarà meglio di CURSOR –

+0

@Fireblade, dovrebbe essere –

+0

I cursori vengono utilizzati quando è necessario elaborare i dati in un ordine particolare, riga per riga. Proprio come hai bisogno di qui. L'SQL basato su set in questo caso sarà inefficiente. Da SQL Server 2012 ci sono le funzioni 'LAG' e' LEAD' che aiutano con questi tipi di query, ma non nel 2008. Naturalmente, se la tua tabella è piccola, allora soluzione basata su set con 'O (n * n)' complessità (auto join in questa risposta) sarebbe eseguito più veloce di 'O (n)' soluzione con il cursore, quindi è necessario controllare le prestazioni con i dati reali. Per lo meno assicurati di avere l'indice corretto. Penso che dovrebbe essere su '(item, ID)'. –

2

si potrebbe fare facilmente con un CTE ricorsiva:

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 

uscita:

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 - Aggiunta un'altra soluzione:

si può fare prendendo l'aiuto di una copia della tabella originale:

Assumendo lo schema e dati sono:

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.prendere una copia dalla tabella originale (utilizzare uno temp tableotable variable) e aggiornare il primo record dalla tabella di riferimento:

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 queste variabili:

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

select @rm=Running_Mul from #tmp where [email protected] 

3.Update la copia:

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

e ora è possibile controllare il risultato:

select * from #tmp 
drop table #tmp 

Risultato:

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

"Facilmente" ... (Una risposta valida, però.) – usr

+0

@ usr, non era bello dire che sì? (Intendo la parola che ho usato). Vorrei non averlo usato. tuttavia penso che non sia importante e non abbia un significato negativo. – jfun

+1

La tua seconda soluzione è chiamata "aggiornamento bizzarro" di Aaron Bertrand http://sqlperformance.com/2012/07/t-sql-queries/running-totals Dice: "Questo non è esattamente garantito per funzionare, e non lo farei mai lo consiglio per il codice di produzione ". In effetti, non vi è alcuna garanzia che questo totale parziale sia calcolato in un ordine particolare. –

6

È possibile trasformare una serie di moltiplicazioni in una serie di aggiunte con il seguente trucco per la matematica:

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

Così MUL(a) è EXP(SUM(LOG(a))).

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

Questo emette sum = 10, product = 24.

I problemi potenziali sono gli errori di arrotondamento e i fattori zero.

Ora è possibile utilizzare uno dei metodi usuali per ottenere un aggregato in esecuzione, ad esempio le funzioni delle finestre. Questo è un problema risolto.

+0

Stavo cercando di implementare anche questo, ma non sono riuscito a farlo. Il problema principale qui è ottenere il valore EDITED precedente e non il valore precedente. Non è solo una moltiplicazione in esecuzione, è in esecuzione formula. Sono arrivato alla conclusione che ci sono solo due modi per ottenere questo: cursori e culti ricorsivi. Se questa è una conclusione errata, sarei molto felice di vedere l'implementazione del trucco di EXP LOG su questa domanda. –

+1

@GiorgiNakeuri non è '(1 + (wac_inc/100))' il fattore che vuoi moltiplicare aggregare? Sarebbe 'EXP (SUM (LOG ((1 + (wac_inc/100)))))'. – usr

+1

grazie mille! Finalmente sono riuscito a farlo. +1 –

4

Per motivi di completezza, ecco una soluzione completa per SQL Server 2012 che utilizza il trucco EXP(SUM(LOG(val))) suggerito da @usr in un'altra risposta.

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; 

set di risultati

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 

Se SQL Server 2012 è disponibile, questa finestra SUM è molto efficiente. Per le versioni precedenti qualsiasi soluzione basata su set risulterebbe nella complessità O(n*n), il che significa che il cursore sarebbe un modo migliore. Ecco un ottimo articolo di Aaron Bertrand confrontando diverse modalità di calcolo totali parziali: domanda http://sqlperformance.com/2012/07/t-sql-queries/running-totals o giù di lì: Calculate running total/running balance

Naturalmente, se la tabella è piccolo, quindi impostare a base di soluzione con O(n*n) complessità può correre più veloce di O(n) soluzione con il cursore a causa del sovraccarico del cursore, quindi è necessario verificare le prestazioni con i dati reali.

+0

Non c'è bisogno di ricorrere qui. Vedi la mia modifica. –

+0

@GiorgiNakeuri, non c'è ricorsione qui. Ho appena inserito la base di riferimento con i dati principali per dargli un punto di partenza per il totale parziale. La seconda soluzione per SQL Server 2012 in EDIT1 in cui la linea di base 'JOIN' con i dati principali potrebbe essere migliore di questa' UNION', perché dovrebbe essere in grado di utilizzare gli indici in modo più efficiente. La mia variante 'UNION' potrebbe richiedere un ordinamento extra. È necessario controllare il piano di esecuzione. –

+0

Scusa, quando vedo sindacato tutto ciò che ho combattuto è stato ricorsione e non ho notato che non c'era nessun join su cte. –