sql >> Base de Datos >  >> RDS >> PostgreSQL

Recuento ordenado de repeticiones/duplicados consecutivos

Caso de prueba

Primero, una forma más útil de presentar sus datos, o incluso mejor, en un sqlfiddle , listo para jugar 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);

Consulta simplificada

Dado que no está claro, asumo solo lo anterior como se indica.
A continuación, simplifiqué su consulta para llegar 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;

Ahora, si bien es bueno y brillante usar SQL puro, esto será mucho más rápido con una función plpgsql, porque puede hacerlo en un solo escaneo de tabla donde esta consulta necesita al menos tres escaneos.

Más rápido con la función 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$;

Llamar:

SELECT * FROM x.f_repeat_ct();

Asegúrese de calificar en la tabla los nombres de sus columnas en todo momento en este tipo de función plpgsql, porque usamos los mismos nombres como parámetros de salida que tendrían prioridad si no se calificaran.

Miles de millones de filas

Si tienes miles de millones de filas , es posible que desee dividir esta operación. Cito el manual aquí:

Nota:La implementación actual de RETURN NEXT y RETURN QUERY almacena todo el conjunto de resultados antes de regresar de la función, como se explicó anteriormente. Eso significa que si una función PL/pgSQL produce un conjunto de resultados muy grande, el rendimiento puede ser deficiente:los datos se escribirán en el disco para evitar el agotamiento de la memoria, pero la función en sí no regresará hasta que se haya generado todo el conjunto de resultados. Una versión futura de PL/pgSQL podría permitir a los usuarios definir funciones de devolución de conjuntos que no tengan esta limitación. Actualmente, el punto en el que los datos comienzan a escribirse en el disco está controlado por la variable de configuración work_mem. Los administradores que tienen suficiente memoria para almacenar conjuntos de resultados más grandes en la memoria deben considerar aumentar este parámetro.

Considere calcular filas para un sistema a la vez o establezca un valor lo suficientemente alto para work_mem para hacer frente a la carga. Siga el enlace proporcionado en la cita para obtener más información sobre work_mem.

Una forma sería establecer un valor muy alto para work_mem con SET LOCAL en su función, que solo es efectivo para la transacción actual. Agregué una línea comentada en la función. no configúrelo muy alto a nivel mundial, ya que esto podría destruir su servidor. Lee el manual.