sql >> Base de Datos >  >> RDS >> Mysql

¿Método para encontrar lagunas en los datos de series temporales en MySQL?

Para empezar, permítanos resumir el número de entradas por hora en su tabla.

SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
       COUNT(*) samplecount
  FROM table
 GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)

Ahora, si registra algo cada seis minutos (diez veces por hora), todos sus valores de recuento de muestras deberían ser diez. Esta expresión:CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) parece peludo, pero simplemente trunca las marcas de tiempo a la hora en que ocurren al poner a cero el minuto y el segundo.

Esto es razonablemente eficiente y lo ayudará a comenzar. Es muy eficiente si puede colocar un índice en su columna entry_time y restringir su consulta a, digamos, muestras de ayer como se muestra aquí.

SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
       COUNT(*) samplecount
  FROM table
 WHERE entry_time >= CURRENT_DATE - INTERVAL 1 DAY
   AND entry_time < CURRENT_DATE
 GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)

Pero no sirve de mucho para detectar horas enteras que pasan sin muestras. También es un poco sensible al jitter en su muestreo. Es decir, si su muestra de la parte superior de la hora a veces se adelanta medio segundo (10:59:30) y, a veces, se atrasa medio segundo (11:00:30), sus conteos de resumen por hora estarán fuera de lugar. Entonces, este resumen de horas (o resumen de días, o resumen de minutos, etc.) no es a prueba de balas.

Necesita una consulta de autounión para obtener las cosas perfectamente bien; es un poco más como una bola de pelo y no tan eficiente.

Empecemos por crearnos una tabla virtual (subconsulta) como esta con muestras numeradas. (Esto es un problema en MySQL; algunos otros DBMS costosos lo hacen más fácil. No importa).

  SELECT @sample:[email protected]+1 AS entry_num, c.entry_time, c.value
    FROM (
        SELECT entry_time, value
      FROM table
         ORDER BY entry_time
    ) C,
    (SELECT @sample:=0) s

Esta pequeña tabla virtual proporciona número_de_entrada, hora_de_entrada, valor.

Siguiente paso, lo unimos a sí mismo.

SELECT one.entry_num, one.entry_time, one.value, 
       TIMEDIFF(two.value, one.value) interval
  FROM (
     /* virtual table */
  ) ONE
  JOIN (
     /* same virtual table */
  ) TWO ON (TWO.entry_num - 1 = ONE.entry_num)

Esto alinea las dos tablas contiguas entre sí compensadas por una sola entrada, regida por la cláusula ON de JOIN.

Finalmente elegimos los valores de esta tabla con un interval más grande que su umbral, y están los tiempos de las muestras justo antes de las que faltan.

La consulta general de autounión es esta. Te dije que era una bola de pelo.

SELECT one.entry_num, one.entry_time, one.value, 
       TIMEDIFF(two.value, one.value) interval
  FROM (
    SELECT @sample:[email protected]+1 AS entry_num, c.entry_time, c.value
      FROM (
          SELECT entry_time, value
            FROM table
           ORDER BY entry_time
      ) C,
      (SELECT @sample:=0) s
  ) ONE
  JOIN (
    SELECT @sample2:[email protected]+1 AS entry_num, c.entry_time, c.value
      FROM (
          SELECT entry_time, value
            FROM table
           ORDER BY entry_time
      ) C,
      (SELECT @sample2:=0) s
  ) TWO ON (TWO.entry_num - 1 = ONE.entry_num)

Si tiene que hacer esto en producción en una tabla grande, es posible que desee hacerlo para un subconjunto de sus datos. Por ejemplo, podría hacerlo todos los días para las muestras de los dos días anteriores. Esto sería decentemente eficiente y también aseguraría que no pasara por alto ninguna muestra faltante justo a la medianoche. Para hacer esto, sus pequeñas tablas virtuales numeradas por filas se verían así.

  SELECT @sample:[email protected]+1 AS entry_num, c.entry_time, c.value
    FROM (
        SELECT entry_time, value
      FROM table
         ORDER BY entry_time
         WHERE entry_time >= CURRENT_DATE - INTERVAL 2 DAY
           AND entry_time < CURRENT_DATE /*yesterday but not today*/
    ) C,
    (SELECT @sample:=0) s