2009-08-13 6 views
1

J'ai un ensemble de données composé de valeurs horodatées et de valeurs absolues (mètres). Parfois, les valeurs du compteur sont réinitialisées à zéro, ce qui signifie que je dois parcourir et calculer un delta un par un, puis l'additionner pour obtenir le total pour une période donnée.Méthode efficace pour calculer la somme des deltas entre des rangées consécutives?

Par exemple:

Timestamp  Value 
2009-01-01  100 
2009-01-02  105 
2009-01-03  120 
2009-01-04  0 
2009-01-05  9 

le total ici est 29, calculé comme suit:

(105 - 100) + (120 - 105) + (0) + (9 - 0) = 29 

J'utilise un serveur MS-SQL pour cela, et ouvert à toutes les suggestions.

En ce moment, je suis en utilisant un curseur pour ce faire, qui vérifie que le delta est pas négatif, et totalise en place:

DECLARE CURSOR curTest CURSOR FAST_FORWARD FOR 
    SELECT value FROM table ORDER BY timestamp 
OPEN curTest 
DECLARE @delta bigint, @current bigint, @last bigint 
SET @delta = 0 

FETCH curTest INTO @current 
WHILE @@FETCH_STATUS = 0 
BEGIN 
    IF (@current IS NOT NULL) AND (@current > 0) 
    BEGIN 
     IF (@last IS NOT NULL) AND (@current > @last) 
      SET @delta = @delta + (@current - @last) 
     SET @last = @current 

     FETCH curTest INTO @current 
    END 
END 

CLOSE curTest 
DEALLOCATE curTest 

Ce serait bien d'obtenir un ensemble de données comme :

Timestamp  Value LastValue 
2009-01-01  100  NULL 
2009-01-02  105  100 
2009-01-03  120  105 
2009-01-04  0  120 
2009-01-05  9  0 

comme il serait alors facile de saisir les deltas, filtre pour (valeur> LastValue) et faire une SOMME().

J'ai essayé:

SELECT m1.timestamp, m1.value, 
    (SELECT TOP 1 m2.value FROM table WHERE m2.timestamp < m1.timestamp ORDER BY m2.timestamp DESC) as LastValue 
FROM table 

, mais cela se fait avéré être plus lent que le curseur: Quand je lance ces ensemble en studio SQL avec « plan d'exécution du spectacle » sur, le coût relatif de ce 100% (avec 7 ou 8 opérations - la majorité dans un index indexé en cluster sur horodatage), et le curseur est 0% (avec 3 opérations).

(Ce que je ne montre pas ici pour simplifier, c'est que j'ai plusieurs ensembles de nombres différents, avec une clé étrangère dans ce tableau aussi - il y a donc toujours une clause WHERE qui limite à un ensemble spécifique. plusieurs endroits où je calcule ces totaux pour une période de temps donnée pour plusieurs ensembles à la fois, et ainsi il devient tout à fait le goulot d'étranglement des performances.La méthode sans curseur peut également être facilement modifiée à GROUP BY la clé et retourner tous les ensembles mais cela est en fait encore plus lent dans mes tests que de courir le curseur plusieurs fois, car il y a le surcroît de travail des opérations GROUP BY et SUM(), en plus de ralentir globalement.)

Répondre

4

Il en va de même ...

create table #temp ([timestamp] date,value int); 
insert into #temp (timestamp,value) values ('2009-01-01',100) 
insert into #temp (timestamp,value) values ('2009-01-02',105) 
insert into #temp (timestamp,value) values ('2009-01-03',120) 
insert into #temp (timestamp,value) values ('2009-01-04',0) 
insert into #temp (timestamp,value) values ('2009-01-05',9); 

with numbered as 
(
    select ROW_NUMBER() over (order by timestamp) id,value from #temp 
) 
select sum(n1.value-n2.value) from numbered n1 join numbered n2 on n1.id=n2.id+1 where n1.value!=0 

drop table #temp; 

Le résultat est 29, tel que spécifié.

+0

+1 pour l'utilisation de row_number() –

2

Commencez par row_number, puis Rejoins-toi.

with numbered as 
(
SELECT value, row_number() over (order by timestamp) as Rownum 
FROM table 
) 
select sum(n1.value - n2.value) 
from numbered n1 
    join 
    numbered n2 on n1.Rownum = n2.Rownum +1 

... En fait, vous ne souhaitez que ramasser augmente ... donc mettre une clause WHERE, en disant "Où n1.value> n2.value".

Et ... assurez-vous que je les ai mis dans le bon sens ... Je l'ai juste changé de -1 à +1, parce que je pense que je l'avais retourné.

Facile!

Rob

0

Il y a trop de jointures inutiles dans votre algorithme.

Calculer la différence entre chaque relevé de compteur et sa lecture ultérieure est un gaspillage de ressources. En tant qu'exemple concret, imaginez si ma compagnie d'électricité lisait chaque jour mon compteur en fonction de la quantité d'électricité utilisée et additionnait les valeurs quotidiennes pour déterminer mon total mensuel - cela n'a tout simplement aucun sens. Ils déterminent simplement le total en fonction de la valeur initiale et de la valeur finale!

Calculez simplement la différence entre la première et la dernière lecture et ajustez pour tenir compte des «réinitialisations». Votre formule devient simplement:

total value = (final value) - (initial value) 
       + (miscellaneous reductions in value, i.e. resets) 
total value = (9) - (100) + (120) 
      = 29 

Il est trivial de trouver la valeur finale et la valeur initiale. Il suffit de trouver le montant total par lequel «compteur» a été réduit lors des «réinitialisations», et ajouter cela au total. À moins qu'il n'y ait plus d'enregistrements de réinitialisation que d'enregistrements de mesure, cela sera toujours plus efficace.

Pour emprunter à la solution de Spender, la valeur 'reset' pourrait être calculée par

create table... 

select sum(n1.value-n2.value) from numbered n1 join numbered n2 
    on n1.id=n2.id+1 where n1.value=0 //note value=0 rather than value!=0 
Questions connexes