Caso di prova
Innanzitutto, un modo più utile per presentare i tuoi dati, o meglio ancora, in un sqlfiddle , pronto per giocare con:
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);
Richiesta semplificata
Dal momento che non è chiaro, presumo solo quanto sopra come dato.
Successivamente, ho semplificato la tua query per arrivare 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;
Ora, mentre è bello e brillante usare SQL puro, sarà molto più veloce con una funzione plpgsql, perché può farlo in un'unica scansione di tabella in cui questa query richiede almeno tre scansioni.
Più veloce con la funzione 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$;
Chiama:
SELECT * FROM x.f_repeat_ct();
Assicurati di qualificare sempre come tabella i nomi delle tue colonne in questo tipo di funzione plpgsql, perché usiamo gli stessi nomi dei parametri di output che avrebbero la precedenza se non qualificati.
Miliardi di righe
Se hai miliardi di righe , potresti voler dividere questa operazione. Cito il manuale qui:
Nota:l'attuale implementazione di RETURN NEXT
e RETURN QUERY
memorizza l'intero set di risultati prima di tornare dalla funzione, come discusso sopra. Ciò significa che se una funzione PL/pgSQL produce un set di risultati molto grande, le prestazioni potrebbero essere scarse:i dati verranno scritti su disco per evitare l'esaurimento della memoria, ma la funzione stessa non verrà restituita fino a quando l'intero set di risultati non sarà stato generato. Una versione futura di PL/pgSQL potrebbe consentire agli utenti di definire funzioni di restituzione di set che non hanno questa limitazione. Attualmente, il punto in cui i dati iniziano a essere scritti su disco è controllato dalla variabile work_memconfiguration. Gli amministratori che dispongono di memoria sufficiente per archiviare in memoria set di risultati più grandi dovrebbero prendere in considerazione l'aumento di questo parametro.
Prendi in considerazione il calcolo delle righe per un sistema alla volta o imposta un valore sufficientemente alto per work_mem
per far fronte al carico. Segui il link fornito nella citazione per ulteriori informazioni su work_mem.
Un modo sarebbe impostare un valore molto alto per work_mem
con SET LOCAL
nella tua funzione, che è efficace solo per la transazione corrente. Ho aggiunto una riga commentata nella funzione. non impostalo molto alto a livello globale, in quanto ciò potrebbe danneggiare il tuo server. Leggi il manuale.