2009-06-16 7 views
7

J'ai une table dans SQL Server qui stocke les statistiques pour un morceau de matériel, les lignes dans le tableau représentent des données pour une seconde donnée. Il contient par exemple, ces colonnes:T-SQL Time Averaging

timestamp (DateTime) 
value (int) 

Ce que je veux faire est de sélectionner les données de la table pour une plage de date/heure donnée, mais le retourner de telle sorte que il est en moyenne pour une période de temps donnée (tel que 1 minute, 5 minutes, 1 jour etc.) entre la gamme donnée. Donc pendant une heure j'aurais 60 rangées de moyennes d'une minute.

Par où commencer? Quelqu'un a des points ou des idées?

Répondre

9

Vous pouvez effectuer une sélection et un regroupement sur une DatePart de votre horodatage.

Par exemple:

SELECT 
    DATEPART(hh, [timestamp]), 
    DATEPART(mi, [timestamp]), 
    AVG([value]) 
FROM 
    YourTable 
WHERE 
    [timestamp] BETWEEN '2009-01-01 00:00:00.000' AND '2009-02-01 00:00:00.000' 
GROUP BY 
    DATEPART(hh, [timestamp]), 
    DATEPART(mi, [timestamp]) 

EDIT: Pour votre temps plus complexe couvre comme 5 minutes, vous pouvez faire une division sur le datepart comme suit.

DATEPART(mi, [timestamp])/5 * 5 
+0

+1 belle requête et facile à lire – Andomar

+0

Cela fonctionne un merci régal! – Lloyd

5
WITH cal(m) AS 
     (
     SELECT 1 
     UNION ALL 
     SELECT m + 1 
     FROM cal 
     WHERE m < 60 
     ) 
SELECT DATEADD(minute, m, @start), AVG(value) 
FROM cal 
LEFT JOIN 
     timestamp 
ON  timestamp > DATEADD(minute, m, @start) 
     AND timestamp <= DATEADD(minute, m + 1, @start) 
GROUP BY 
     m 

Cela permet de sélectionner des moyennes pour toutes les minutes dans une heure donnée, même ceux pour lesquels il n'y a pas de documents.

+0

+1. joli. ... –

+0

Construire une table numérique avec un CTE récursif, pas vu avant - je l'aime! – AakashM

+0

+1 pour le CTE! –

1

En plus du message par Robin Day, vous pouvez regrouper par intervalles de 5 minutes comme:

GROUP BY 
    DATEPART(hh, [timestamp]), 
    DATEPART(mi, [timestamp])/5 

Et si vous souhaitez étendre sur plusieurs jours, le groupe sur dy, pour le jour de l'année:

GROUP BY 
    DATEPART(dy, [timestamp]), 
    DATEPART(hh, [timestamp]), 
    DATEPART(mi, [timestamp])/5 
1

Si vous souhaitez avoir un taux de lecture/écriture élevé pour ces données, vous pouvez envisager une vue indexée. J'ai utilisé cette approche partout pour agréger par seaux de temps. Je viens autour de blogging the example, voici le code:

create table timeSeries (
    timeSeriesId int identity primary key clustered 
    ,updateDate datetime not null 
    ,payload float not null 
) 

insert timeSeries values ('2009-06-16 12:00:00', rand()) 
insert timeSeries values ('2009-06-16 12:00:59', rand()) 
insert timeSeries values ('2009-06-16 12:01:00', rand()) 
insert timeSeries values ('2009-06-16 12:59:00', rand()) 
insert timeSeries values ('2009-06-16 01:00:00', rand()) 
insert timeSeries values ('2009-06-16 1:30:00', rand()) 
insert timeSeries values ('2009-06-16 23:59:00', rand()) 
insert timeSeries values ('2009-06-17 00:01:00', rand()) 
insert timeSeries values ('2009-06-17 00:01:30', rand()) 


create view timeSeriesByMinute_IV with schemabinding as 
select 
    dayBucket = datediff(day, 0, updateDate) 
    ,minuteBucket = datediff(minute, 0, (updateDate - datediff(day, 0, updateDate))) 
    ,payloadSum = sum(payLoad) 
    ,numRows = count_big(*) 
from dbo.timeSeries 
group by 
    datediff(day, 0, updateDate) 
    ,datediff(minute, 0, (updateDate - datediff(day, 0, updateDate))) 
go 

create unique clustered index CU_timeSeriesByMinute_IV on timeSeriesByMinute_IV (dayBucket, minuteBucket) 
go 


create view timeSeriesByMinute as 
select 
    dayBucket 
    ,minuteBucket 
    ,payloadSum 
    ,numRows 
    ,payloadAvg = payloadSum/numRows 
from dbo.timeSeriesByMinute_IV with (noexpand) 
go 

declare @timeLookup datetime, @dayBucket int, @minuteBucket int 
select 
    @timeLookup = '2009-06-16 12:00:00' 
    ,@dayBucket = datediff(day, 0, @timeLookup) 
    ,@minuteBucket = datediff(minute, 0, (@timeLookup - datediff(day, 0, @timeLookup))) 

select * from timeSeriesByMinute where dayBucket = @dayBucket and minuteBucket = @minuteBucket 

Vous pouvez voir la recherche d'exemple à la fin du bloc de code. Il est clair que vous pouvez définir des plages à interroger au lieu de simplement rechercher une paire dayBucket/minuteBucket particulière.

1

Je ne pouvais pas obtenir la réponse de Quassnoi travailler sans les modifications suivantes:

WITH cal(m) AS 
    (
    SELECT 1 
    UNION ALL 
    SELECT m + 1 
    FROM cal 
    WHERE m < 60 
    ) 
SELECT DATEADD(minute, m, @start) m, AVG(value) 
FROM cal 
LEFT JOIN 
    YourTable 
ON  timestamp > DATEADD(minute, m, @start) 
    AND timestamp <= DATEADD(minute, m + 1, @start) 
GROUP BY 
    m