2009-06-16 3 views
7

Ich habe eine Tabelle in SQL Server, die Statistiken für ein Stück Hardware speichert, Zeilen in der Tabelle repräsentieren Daten für eine bestimmte Sekunde. Es enthält zum Beispiel diese Säulen:T-SQL-Zeitmittelung

timestamp (DateTime) 
value (int) 

Was möchte ich tun, ist, die Daten aus der Tabelle für einen bestimmten Datums-/Zeitbereich auswählen, aber es so zurückgeben, dass sie Mittelwerte für einen bestimmten Zeitraum (wie 1 Minute, 5 Minuten, 1 Tag usw.) zwischen dem angegebenen Bereich. Für eine Stunde hätte ich 60 Reihen mit 1 Minute Durchschnitt.

Wo fange ich damit an? Irgendwelche irgendwelche Punkte oder Ideen?

Antwort

9

Sie können eine Auswahl und Gruppierung nach einem DatePart Ihres Zeitstempels vornehmen.

Beispiel:

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: für Ihre komplexeren Zeit wie 5 Minuten reicht, können Sie auf dem Datumsteil eine Kluft tun wie folgt.

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

+1 schöne Abfrage und einfach – Andomar

+0

Das funktioniert ein Genuss dank zu lesen! – 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 

Dies wird innerhalb einer bestimmten Stunde für alle Minuten mittelt wählen, auch diejenigen, für die es keine Aufzeichnungen gibt.

+0

+1. Schön. ... –

+0

Eine Zahlentabelle mit einem rekursiven CTE aufbauen, das vorher nicht gesehen - ich mag es! – AakashM

+0

+1 für den CTE! –

1

Neben dem Beitrag von Robin Day, können Sie Gruppe um 5-Minuten-Intervallen wie:

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

Und wenn Sie möchten, dass mehrere Tage erstrecken, Gruppe auf dy, für den Tag des Jahres:

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

Wenn Sie für diese Daten ein hohes Lese-/Schreib-Verhältnis haben, sollten Sie eine indizierte Ansicht in Betracht ziehen. Ich habe diesen Ansatz überall benutzt, um nach Zeiteinheiten zu aggregieren. Ich habe gerade um zu blogging the example, hier ist der 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 

Sie können das Beispiel Nachschlag am Ende des Codeblocks sehen. Natürlich können Sie Bereiche definieren, um Query abzufragen, anstatt nur nach einem bestimmten dayBucket/minuteBucket-Paar zu suchen.

1

Ich konnte nicht Quassnoi Antwort erhalten, ohne dass die folgenden Änderungen zu arbeiten:

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