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 deRETURN NEXT
eRETURN 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.