web-dev-qa-db-fra.com

SELECT/GROUP BY - segments de temps (10 secondes, 30 secondes, etc.)

J'ai une table (MySQL) qui capture des échantillons toutes les n secondes. La table comporte de nombreuses colonnes, mais seules deux sont nécessaires: un horodatage (de type TIMESTAMP) et un compte (de type INT). 

Ce que je voudrais faire, c'est obtenir des sommes et des moyennes de la colonne de comptage sur une plage de fois. Par exemple, j'ai des échantillons enregistrés toutes les 2 secondes, mais j'aimerais connaître la somme de la colonne de comptage pour tous les échantillons dans une fenêtre de 10 ou 30 secondes pour tous les échantillons.

Voici un exemple de données:

 + --------------------- + ----------------- + 
 | horodatage | nombre | 
 + --------------------- + ----------------- + 
 | 2010-06-15 23:35:28 | 1 | 
 | 2010-06-15 23:35:30 | 1 | 
 | 2010-06-15 23:35:30 | 1 | 
 | 2010-06-15 23:35:30 | 942 | 
 | 2010-06-15 23:35:30 | 180 | 
 | 2010-06-15 23:35:30 | 4 | 
 | 2010-06-15 23:35:30 | 52 | 
 | 2010-06-15 23:35:30 | 12 | 
 | 2010-06-15 23:35:30 | 1 | 
 | 2010-06-15 23:35:30 | 1 | 
 | 2010-06-15 23:35:33 | 1468 | 
 | 2010-06-15 23:35:33 | 247 | 
 | 2010-06-15 23:35:33 | 1 | 
 | 2010-06-15 23:35:33 | 81 | 
 | 2010-06-15 23:35:33 | 16 | 
 | 2010-06-15 23:35:35 | 1828 | 
 | 2010-06-15 23:35:35 | 214 | 
 | 2010-06-15 23:35:35 | 75 | 
 | 2010-06-15 23:35:35 | 8 | 
 | 2010-06-15 23:35:37 | 1799 | 
 | 2010-06-15 23:35:37 | 24 | 
 | 2010-06-15 23:35:37 | 11 | 
 | 2010-06-15 23:35:37 | 2 | 
 | 2010-06-15 23:35:40 | 575 | 
 | 2010-06-15 23:35:40 | 1 | 
 | 2010-06-17 10:39:35 | 2 | 
 | 2010-06-17 10:39:35 | 2 | 
 | 2010-06-17 10:39:35 | 1 | 
 | 2010-06-17 10:39:35 | 2 | 
 | 2010-06-17 10:39:35 | 1 | 
 | 2010-06-17 10:39:40 | 35 | 
 | 2010-06-17 10:39:40 | 19 | 
 | 2010-06-17 10:39:40 | 37 | 
 | 2010-06-17 10:39:42 | 64 | 
 | 2010-06-17 10:39:42 | 3 | 
 | 2010-06-17 10:39:42 | 31 | 
 | 2010-06-17 10:39:42 | 7 | 
 | 2010-06-17 10:39:42 | 246 | 
 + --------------------- + ----------------- + 

La sortie que je voudrais (d'après les données ci-dessus) devrait ressembler à ceci:

 + --------------------- + ----------------- + 
 | 2010-06-15 23:35:00 | 1 | # Ceci est la somme pour la plage 00 - 30 secondes 
 | 2010-06-15 23:35:30 | 7544 | # Ceci est la somme pour la plage de 30 à 60 secondes 
 | 2010-06-17 10:39:35 | 450 | # Ceci est la somme pour la plage de 30 à 60 secondes 
 + --------------------- + ------------ ----- + 

J'ai utilisé GROUP BY pour rassembler ces chiffres à la seconde ou à la minute, mais je n'arrive pas à comprendre la syntaxe permettant d'obtenir le bon fonctionnement des commandes GROUP BY de moins d'une minute ou de quelques secondes.

Je vais surtout utiliser cette requête pour siphonner les données de cette table dans une autre table.

Merci!

38
Eric Anderson

J'ai essayé la solution Hammerite dans mon projet, mais cela n'a pas bien fonctionné lorsqu'il manquait des échantillons de la série. Voici un exemple de requête censée sélectionner timestamp (ts), nom d'utilisateur et mesure moyenne dans metric_table et regrouper les résultats par intervalles de 27 minutes:

select 
    min(ts), 
    user_name, 
    sum(measure) / 27
from metric_table 
where 
    ts between date_sub('2015-03-17 00:00:00', INTERVAL 2160 MINUTE) and '2015-03-17 00:00:00' 

group by unix_timestamp(ts) div 1620, user_name 
order by ts, user_name
;

Remarque: 27 minutes (en sélection) = 1620 secondes (en groupe par), 2160 minutes = 3 jours (c'est la plage de temps)

Lorsque j'ai exécuté cette requête sur une série chronologique dans laquelle des échantillons étaient enregistrés de manière irrégulière (autrement dit: pour un horodatage donné, il n'y avait aucune garantie de trouver des valeurs de mesure pour tous les noms d'utilisateur), les résultats n'étaient pas estampillés en fonction de l'intervalle (n'étaient pas placés toutes les 27 minutes). Je soupçonne que cela était dû au fait que min (ts) a renvoyé dans certains groupes un horodatage supérieur au sol prévu (intervalle ts0 + i *). J'ai modifié l'ancienne requête à celle-ci:

select 
    from_unixtime(unix_timestamp(ts) - unix_timestamp(ts) mod 1620) as ts1, 
    user_name, 
    sum(measure) / 27
from metric_table
where 
    ts between date_sub('2015-03-17 00:00:00', INTERVAL 2160 MINUTE) and '2015-03-17 00:00:00' 

group by ts1, user_name 
order by ts1, user_name
;

et cela fonctionne bien même lorsque les échantillons manquent. Je pense que c’est parce qu’une fois que les calculs temporels sont déplacés, il est garanti que ts1 s’alignera sur les pas de temps.

6
mac13k

Une autre solution.

Pour faire la moyenne sur n'importe quel intervalle, vous pouvez convertir votre dt en horodatage et le grouper par modulo selon votre intervalle (7 secondes dans l'exemple).

select FROM_UNIXTIME(
    UNIX_TIMESTAMP(dt_record) - UNIX_TIMESTAMP(dt_record) mod 7
) as dt, avg(1das4hrz) from `meteor-m2_msgi`
where dt_record>='2016-11-13 05:00:00'
and dt_record < '2016-11-13 05:02:00'
group by FROM_UNIXTIME(
    UNIX_TIMESTAMP(dt_record) - UNIX_TIMESTAMP(dt_record) mod 7);

Pour montrer comment cela fonctionne, je prépare une demande avec des calculs.

select dt_record, minute(dt_record) as mm, SECOND(dt_record) as ss,
UNIX_TIMESTAMP(dt_record) as uxt, UNIX_TIMESTAMP(dt_record) mod 7 as ux7,
FROM_UNIXTIME(
    UNIX_TIMESTAMP(dt_record) - UNIX_TIMESTAMP(dt_record) mod 7) as dtsub,
column from `yourtable` where dt_record>='2016-11-13 05:00:00'
and dt_record < '2016-11-13 05:02:00';

+---------------------+--------------------+
| dt                  | avg(column)        |
+---------------------+--------------------+
| 2016-11-13 04:59:43 |  25434.85714285714 |
| 2016-11-13 05:00:42 |  5700.728813559322 |
| 2016-11-13 05:01:41 |  950.1016949152543 |
| 2016-11-13 05:02:40 |  4671.220338983051 |
| 2016-11-13 05:03:39 | 25468.728813559323 |
| 2016-11-13 05:04:38 |  43883.52542372881 |
| 2016-11-13 05:05:37 | 24589.338983050846 |
+---------------------+--------------------+


+---------------------+-----+-----+------------+------+---------------------+----------+
| dt_record           | mm  | ss  | uxt        | ux7  | dtsub               | column   |
+---------------------+------+-----+------------+------+---------------------+----------+
| 2016-11-13 05:00:00 |   0 |   0 | 1479002400 |    1 | 2016-11-13 04:59:59 |    36137 |
| 2016-11-13 05:00:01 |   0 |   1 | 1479002401 |    2 | 2016-11-13 04:59:59 |    36137 |
| 2016-11-13 05:00:02 |   0 |   2 | 1479002402 |    3 | 2016-11-13 04:59:59 |    36137 |
| 2016-11-13 05:00:03 |   0 |   3 | 1479002403 |    4 | 2016-11-13 04:59:59 |    34911 |     
| 2016-11-13 05:00:04 |   0 |   4 | 1479002404 |    5 | 2016-11-13 04:59:59 |    34911 |
| 2016-11-13 05:00:05 |   0 |   5 | 1479002405 |    6 | 2016-11-13 04:59:59 |    34911 |
| 2016-11-13 05:00:06 |   0 |   6 | 1479002406 |    0 | 2016-11-13 05:00:06 |    33726 |
| 2016-11-13 05:00:07 |   0 |   7 | 1479002407 |    1 | 2016-11-13 05:00:06 |    32581 |
| 2016-11-13 05:00:08 |   0 |   8 | 1479002408 |    2 | 2016-11-13 05:00:06 |    32581 |
| 2016-11-13 05:00:09 |   0 |   9 | 1479002409 |    3 | 2016-11-13 05:00:06 |    31475 |
+---------------------+-----+-----+------------+------+---------------------+----------+

Quelqu'un peut-il suggérer quelque chose de plus rapide?

2
Wera

Très étrange mais en utilisant la solution ici:

Moyenne des données toutes les 5 minutes dans les temps impartis

Nous pouvons suggérer quelque chose comme:

select convert(
              (min(dt_record) div 50)*50 - 20*((convert(min(dt_record), 
               datetime) div 50) mod 2), datetime)  as dt, 
       avg(1das4hrz) 
from `meteor-m2_msgi`
where dt_record>='2016-11-13 05:00:00'
       and dt_record < '2016-11-14 00:00:00' 
group by convert(dt_record, datetime) div 50;


select (
convert(
min(dt_record), datetime) div 50)*50 - 20*(
(convert(min(dt_record), datetime) div 50) mod 2
) as dt,
avg(column) from `your_table`
where dt_record>='2016-11-13 05:00:00'
and dt_record < '2016-11-14 00:00:00'
group by convert(dt_record, datetime) div 50;

50 est parce que 1/2 de NORMALE minute a 30 secondes tandis que 'FORMAT DE DATE INTEGER' suppose que nous divisions par 50

0
Wera