Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Método de encontrar lacunas nos dados de séries temporais no MySQL?


Para começar, vamos resumir o número de entradas por hora em sua tabela.
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)

Agora, se você registrar algo a cada seis minutos (dez vezes por hora), todos os seus valores de contagem de amostras devem ser dez. Esta expressão:CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) parece cabeludo, mas simplesmente trunca seus timestamps para a hora em que eles ocorrem zerando o minuto e o segundo.

Isso é razoavelmente eficiente e você começará. É muito eficiente se você puder colocar um índice em sua coluna entry_time e restringir sua consulta para, digamos, amostras de ontem, conforme mostrado aqui.
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)

Mas não é muito bom para detectar horas inteiras que passam com amostras perdidas. Também é um pouco sensível ao jitter em sua amostragem. Ou seja, se sua amostra de hora mais alta às vezes estiver meio segundo adiantada (10:59:30) e às vezes meio segundo atrasada (11:00:30), suas contagens de resumo por hora estarão desativadas. Então, essa coisa de resumo de hora (ou resumo de dia, ou resumo de minuto, etc) não é à prova de balas.

Você precisa de uma consulta de auto-junção para obter as coisas perfeitamente corretas; é um pouco mais uma bola de pelo e não tão eficiente.

Vamos começar criando uma tabela virtual (subconsulta) como esta com amostras numeradas. (Isso é uma dor no MySQL; alguns outros DBMSs caros tornam isso mais fácil. Não 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 pequena tabela virtual dá entry_num, entry_time, value.

Próximo passo, nós o juntamos a si mesmo.
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)

Isso alinha as tabelas próximas duas umas das outras compensadas por uma única entrada, regida pela cláusula ON do JOIN.

Finalmente escolhemos os valores desta tabela com um interval maior que o seu limite, e há os tempos das amostras logo antes das que faltam.

A consulta de auto-junção geral é esta. Eu disse que era uma 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)

Se você precisar fazer isso em produção em uma tabela grande, talvez queira fazer isso para um subconjunto de seus dados. Por exemplo, você pode fazer isso todos os dias para as amostras dos dois dias anteriores. Isso seria decentemente eficiente e também garantiria que você não ignorasse nenhuma amostra perdida à meia-noite. Para fazer isso, suas pequenas tabelas virtuais numeradas em linha ficariam assim.
  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