Calcul de la moyenne temporelle T-SQL
-
05-07-2019 - |
Question
J'ai une table dans SQL Server qui stocke les statistiques pour un élément matériel, les lignes de la table représentent les données pour une seconde donnée. Il contient par exemple ces colonnes:
timestamp (DateTime)
value (int)
Ce que je veux faire, c'est sélectionner les données du tableau pour une plage de dates / heures donnée, mais les renvoyer de manière à ce qu'elles soient moyennes pour une période donnée (telle que 1 minute, 5 minutes, 1 jour, etc.) entre la plage donnée. Donc, pendant une heure, j'aurais 60 rangées de moyennes sur 1 minute.
Par quoi dois-je commencer? Quelqu'un a des points ou des idées?
La solution
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 les plages de temps plus complexes telles que 5 minutes, vous pouvez diviser la date du jour de la manière suivante:
DATEPART(mi, [timestamp]) / 5 * 5
Autres conseils
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
Ceci sélectionnera les moyennes pour toutes les minutes d'une heure donnée, même celles pour lesquelles il n'y a pas d'enregistrements.
En plus du message de Robin Day, vous pouvez regrouper par intervalles de 5 minutes, par exemple:
GROUP BY
DATEPART(hh, [timestamp]),
DATEPART(mi, [timestamp]) / 5
Et si vous souhaitez couvrir plusieurs jours, groupe le jour de l’année:
GROUP BY
DATEPART(dy, [timestamp]),
DATEPART(hh, [timestamp]),
DATEPART(mi, [timestamp]) / 5
Si vous envisagez un rapport lecture / écriture élevé pour ces données, vous pouvez envisager une vue indexée. J'ai utilisé cette approche un peu partout pour regrouper des tranches de temps. Je viens de me rendre à bloguant l'exemple , 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 l'exemple de recherche à la fin du bloc de code. Clairement, vous pouvez définir des plages de requête au lieu de rechercher uniquement une paire dayBucket / minuteBucket particulière.
Je ne pouvais pas obtenir la réponse de Quassnoi 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