2012-06-18 14 views
6

Digamos que tenemos una tabla de base de datos con dos columnas, entry_time y value. entry_time es timestamp mientras que value puede ser cualquier otro tipo de datos. Los registros son relativamente consistentes, ingresados ​​en intervalos de aproximadamente x minutos. Sin embargo, durante muchas x veces, no se puede realizar una entrada, lo que produce un "vacío" en los datos.Método de búsqueda de lagunas en los datos de series de tiempo en MySQL?

En términos de eficiencia, ¿cuál es la mejor manera de encontrar estas lagunas de al menos tiempo Y (tanto nuevas como antiguas) con una consulta?

+0

¿Cómo se define una brecha? ¿Tiene un límite estricto sobre cuánto tiempo puede transcurrir entre las entradas? –

+0

Una variable Y. Olvidé especificar eso. – TheDog

Respuesta

15

Para empezar, resumamos 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 muestra deberían ser diez. Esta expresión: CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) se ve peluda pero simplemente trunca tus marcas de tiempo a la hora en que ocurren poniendo a cero el minuto y el segundo.

Esto es razonablemente eficiente y lo ayudará a comenzar. Es muy eficiente si puede poner 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 es muy bueno para detectar horas completas que pasan sin muestras faltantes. También es un poco sensible a la inestabilidad en su muestreo. Es decir, si su muestra del mejor horario es, a veces, medio segundo antes (10:59:30) y, a veces, medio segundo tarde (11:00:30), sus recuentos de resumen por hora estarán desactivados. Por lo tanto, este resumen de horas (o resumen de días, o resumen de minutos, etc.) no es a prueba de balas.

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

Comencemos creando una tabla virtual (subconsulta) como esta con ejemplos numerados. (Este es un dolor en MySQL, y algunos otros DBMS caros hacen que sea 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 da entry_num, entry_time, valor.

El próximo 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 concuerda las tablas siguientes dos entre sí compensado por una sola entrada, que se rige por la cláusula ON de la combinación.

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

La consulta de autocombinación sobre todo es esto. 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 usted tiene que hacer esto en la producción en una mesa grande es posible que desee hacerlo para un subconjunto de los datos. Por ejemplo, puede hacerlo todos los días para las muestras de los dos días anteriores. Esto sería decentemente eficiente, y también se aseguraría de no omitir ninguna muestra faltante justo a la medianoche. Para hacer esto, sus pequeñas tablas virtuales redondas 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 
+0

Muchas gracias por esta solución, aunque estoy confundido en cuanto a qué exactamente @sample: = @ sample + 1 does – TheDog

+0

Esta variable '@ sample' realiza un seguimiento del número de fila. Observe que se inicializa en '(SELECT @sample: = 0)' y se incrementa para cada fila de la tabla. Si tuviera que pagar decenas de miles de dólares por Oracle, podría simplemente decir ROWNUM, pero este es el truco de MySQL para hacer lo mismo. Arcano, ¿eh? –

+0

+1 para una explicación paso a paso – kirugan

1

Una forma muy eficiente de hacerlo es con un procedimiento almacenado utilizando cursores.Creo que esto es más simple y más eficiente que las otras respuestas.

Este procedimiento crea un cursor y lo itera a través de los registros de fecha y hora que está comprobando. Si alguna vez hay una brecha de más de lo que especifique, escribirá el inicio y el final de la brecha en una tabla.

CREATE PROCEDURE findgaps() 
    BEGIN  
    DECLARE done INT DEFAULT FALSE; 
    DECLARE a,b DATETIME; 
    DECLARE cur CURSOR FOR SELECT dateTimeCol FROM targetTable 
          ORDER BY dateTimeCol ASC; 
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;  
    OPEN cur;  
    FETCH cur INTO a;  
    read_loop: LOOP 
     SET b = a; 
     FETCH cur INTO a; 
     IF done THEN 
      LEAVE read_loop; 
     END IF;  
     IF DATEDIFF(a,b) > [range you specify] THEN 
      INSERT INTO tmp_table (gap_begin, gap_end) 
      VALUES (a,b); 
     END IF; 
    END LOOP;   
    CLOSE cur;  
    END; 

En este caso, se supone que existe 'tmp_table'. Podrías definir fácilmente esto como una tabla TEMPORAL en el procedimiento, pero lo dejé fuera de este ejemplo.

Cuestiones relacionadas