PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Contagem ordenada de repetições/duplicatas consecutivas

Caso de teste


Primeiro, uma maneira mais útil de apresentar seus dados - ou ainda melhor, em um sqlfiddle , pronto para jogar com:
CREATE TEMP TABLE data(
   system_measured int
 , time_of_measurement int
 , measurement int
);

INSERT INTO data VALUES
 (1, 1, 5)
,(1, 2, 150)
,(1, 3, 5)
,(1, 4, 5)
,(2, 1, 5)
,(2, 2, 5)
,(2, 3, 5)
,(2, 4, 5)
,(2, 5, 150)
,(2, 6, 5)
,(2, 7, 5)
,(2, 8, 5);

Consulta simplificada


Como ainda não está claro, estou assumindo apenas o que foi dito acima.
Em seguida, simplifiquei sua consulta para chegar a:
WITH x AS (
   SELECT *, CASE WHEN lag(measurement) OVER (PARTITION BY system_measured
                               ORDER BY time_of_measurement) = measurement
                  THEN 0 ELSE 1 END AS step
   FROM   data
   )
   , y AS (
   SELECT *, sum(step) OVER(PARTITION BY system_measured
                            ORDER BY time_of_measurement) AS grp
   FROM   x
   )
SELECT * ,row_number() OVER (PARTITION BY system_measured, grp
                             ORDER BY time_of_measurement) - 1 AS repeat_ct
FROM   y
ORDER  BY system_measured, time_of_measurement;

Agora, embora seja bom e brilhante usar SQL puro, isso será muito mais rápido com uma função plpgsql, porque pode fazê-lo em uma única varredura de tabela onde esta consulta precisa de pelo menos três varreduras.

Mais rápido com a função plpgsql:

CREATE OR REPLACE FUNCTION x.f_repeat_ct()
  RETURNS TABLE (
    system_measured int
  , time_of_measurement int
  , measurement int, repeat_ct int
  )  LANGUAGE plpgsql AS
$func$
DECLARE
   r    data;     -- table name serves as record type
   r0   data;
BEGIN

-- SET LOCAL work_mem = '1000 MB';  -- uncomment an adapt if needed, see below!

repeat_ct := 0;   -- init

FOR r IN
   SELECT * FROM data d ORDER BY d.system_measured, d.time_of_measurement
LOOP
   IF  r.system_measured = r0.system_measured
       AND r.measurement = r0.measurement THEN
      repeat_ct := repeat_ct + 1;   -- start new array
   ELSE
      repeat_ct := 0;               -- start new count
   END IF;

   RETURN QUERY SELECT r.*, repeat_ct;

   r0 := r;                         -- remember last row
END LOOP;

END
$func$;

Ligar:
SELECT * FROM x.f_repeat_ct();

Certifique-se de qualificar seus nomes de coluna em tabelas o tempo todo neste tipo de função plpgsql, porque usamos os mesmos nomes como parâmetros de saída que teriam precedência se não fossem qualificados.

Bilhões de linhas


Se você tem bilhões de linhas , você pode querer dividir esta operação. Cito o manual aqui:

Nota:A implementação atual de RETURN NEXT e RETURN QUERY armazena todo o conjunto de resultados antes de retornar da função, conforme discutido acima. Isso significa que se uma função PL/pgSQL produzir um conjunto de resultados muito grande, o desempenho pode ser ruim:os dados serão gravados em disco para evitar o esgotamento da memória, mas a função em si não retornará até que todo o conjunto de resultados seja gerado. Uma versão futura do PL/pgSQL pode permitir que os usuários definam funções de retorno de conjunto que não tenham essa limitação. Atualmente, o ponto em que os dados começam a ser gravados no disco é controlado pela variável work_memconfiguration. Os administradores que têm memória suficiente para armazenar conjuntos de resultados maiores na memória devem considerar aumentar esse parâmetro.

Considere computar linhas para um sistema por vez ou defina um valor alto o suficiente para work_mem para lidar com a carga. Siga o link fornecido na citação para saber mais sobre work_mem.

Uma maneira seria definir um valor muito alto para work_mem com SET LOCAL em sua função, que só é efetiva para a transação atual. Eu adicionei uma linha comentada na função. não defina-o muito alto globalmente, pois isso pode destruir seu servidor. Leia o manual.