PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Conteggio ordinato di ripetizioni / duplicati consecutivi

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.