2015-10-08 11 views
5

Siamo spiacenti per il cattivo argomento, ma non ero sicuro di come chiamarlo ..somma parziale in T-SQL

Ho una tabella simile a questo:

+-----++-----+ 
| Id ||Count| 
+-----++-----+ 
| 1 || 1 | 
+-----++-----+ 
| 2 || 5 | 
+-----++-----+ 
| 3 || 8 | 
+-----++-----+ 
| 4 || 3 | 
+-----++-----+ 
| 5 || 6 | 
+-----++-----+ 
| 6 || 8 | 
+-----++-----+ 
| 7 || 3 | 
+-----++-----+ 
| 8 || 1 | 
+-----++-----+ 

che sto cercando di fare una selezione da questa tabella in cui ogni volta che la SOMMA di row1 + row2 + row3 (etc) raggiunge 10, allora è un "HIT", e il conteggio ricomincia. Uscita

richiesto:

+-----++-----++-----+ 
| Id ||Count|| HIT | 
+-----++-----++-----+ 
| 1 || 1 || N | Count = 1 
+-----++-----++-----+ 
| 2 || 5 || N | Count = 6 
+-----++-----++-----+ 
| 3 || 8 || Y | Count = 14 (over 10) 
+-----++-----++-----+ 
| 4 || 3 || N | Count = 3 
+-----++-----++-----+ 
| 5 || 6 || N | Count = 9 
+-----++-----++-----+ 
| 6 || 8 || Y | Count = 17 (over 10..) 
+-----++-----++-----+ 
| 7 || 3 || N | Count = 3 
+-----++-----++-----+ 
| 8 || 1 || N | Count = 4 
+-----++-----++-----+ 

Come faccio a fare questo, e con le migliori prestazioni? Non ho idea ..

+0

[guarda qui] (http://stackoverflow.com/a/31497897/3094533) –

+0

Pensiero interessante sull'uso di dense_rank(). Forse questo avrebbe funzionato. Altrimenti temo che un proc memorizzato con un cursore sia probabilmente ciò che è necessario. –

+0

Se 'dense_rank()' non fa il trucco, puoi farlo anche con una vista ricorsiva. Credo che la rotta 'dense_rank()' sarebbe meglio ottimizzata. – JNevill

risposta

3

si potrebbe usare Recursive Queries

Si prega di notare la seguente query assumendo il valore id sono tutte in sequenza, in caso contrario, si prega di utilizzare ROW_NUMBER() per creare un nuovo ID

WITH cte AS (
    SELECT id, [Count], [Count] AS total_count 
    FROM Table1 
    WHERE id = 1 
    UNION ALL 
    SELECT t2.id,t2.[Count], CASE WHEN t1.total_count >= 10 THEN t2.[Count] ELSE t1.total_count + t2.[Count] END 
    FROM Table1 t2 
    INNER JOIN cte t1 
    ON t2.id = t1.id + 1 
) 
SELECT * 
FROM cte 
ORDER BY id 

SQL Fiddle

1

Spero davvero che qualcuno possa mostrarci se è possibile farlo utilizzando le funzioni di finestra straight-forward. Questa è la vera sfida.

Nel frattempo, ecco come lo farei usando la ricorsione. Gestisce gli spazi vuoti nella sequenza e gestisce il caso limite della prima riga già impostato su >= 10.

Ho anche aggiunto l'hint maxrecursion per rimuovere il limite di ricorsione predefinito. Ma sinceramente non so quanto funzionerà con grandi quantità di dati.

with NumberedRows as (
    select Id, Cnt, 
     row_number() over(order by id) as rn 
    from CountTable 
), RecursiveCTE as (
    select Id, Cnt, rn, 
     case when Cnt >= 10 then 0 else Cnt end as CumulativeSum, 
     case when Cnt >= 10 then 'Y' else 'N' end as hit 
    from NumberedRows 
    where rn = 1 
    union all 
    select n.Id, n.Cnt, n.rn, 
     case when (n.Cnt + r.CumulativeSum) >= 10 then 0 else n.Cnt + r.CumulativeSum end as CumulativeSum, 
     case when (n.Cnt + r.CumulativeSum) >= 10 then 'Y' else 'N' end as hit 
    from RecursiveCTE r 
    join NumberedRows n 
     on n.rn = r.rn + 1 
) 
select Id, Cnt, hit 
from RecursiveCTE 
order by Id 
option (maxrecursion 0) 

SQLFiddle Demo

4

Questo è troppo lungo per un commento.

Non è possibile farlo utilizzando le funzioni finestra/analitica, poiché i punti di interruzione non sono noti in anticipo. A volte, è possibile calcolare i punti di interruzione. Tuttavia, in questo caso, i breakpoint dipendono da una funzione non lineare dei valori precedenti (non riesco a pensare ad una parola migliore di "non lineare" in questo momento). Cioè, a volte aggiungendo "1" a un valore precedente ha effetto zero sul calcolo per la riga corrente. A volte ha un grande effetto. L'implicazione è che il calcolo deve iniziare all'inizio e iterare attraverso i dati.

Una piccola modifica al problema sarebbe essere risolvibile utilizzando tali funzioni. Se il problema fosse, invece, di riportare l'importo in eccesso per ciascun gruppo (anziché ricominciare la somma), il problema sarebbe risolvibile usando somme cumulative (e qualche altro trucco).

Le query ricorsive (che altri hanno fornito) o un'operazione sequenziale rappresentano il modo migliore per affrontare questo problema. Sfortunatamente, non ha un metodo basato su set per risolverlo.

1

ne dite di questo utilizzando Totali parziali:

DECLARE @Data TABLE(
    Id INT 
    ,SubTotal INT 
) 


INSERT INTO @Data 
    VALUES(1, 5) 

INSERT INTO @Data 
    VALUES(2, 3) 

INSERT INTO @Data 
    VALUES(3, 4) 

INSERT INTO @Data 
    VALUES(4, 4) 

INSERT INTO @Data 
    VALUES(5, 7) 

DECLARE @RunningTotal INT = 0 
DECLARE @HitCount INT = 0  

SELECT 
     @RunningTotal = CASE WHEN @RunningTotal < 10 THEN @RunningTotal + SubTotal ELSE SubTotal END 
     ,@HitCount = @HitCount + CASE WHEN @RunningTotal >= 10 THEN 1 ELSE 0 END 
     FROM @Data ORDER BY Id 

SELECT @HitCount -- Outputs 2 

Dopo aver riletto la domanda che mi vedo questo non soddisfa l'output richiesto - Lascio la risposta in quanto può essere di qualche utilità a chi cerca per un esempio di una soluzione totale in esecuzione a questo tipo di problema che non ha bisogno di ogni riga taggata con una Y o una N.