sql >> Databasteknik >  >> RDS >> PostgreSQL

Beställt antal på varandra följande upprepningar / dubbletter

Testfall

Först ett mer användbart sätt att presentera din data - eller ännu bättre, i en sqlfiddle , redo att leka med:

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);

Förenklad fråga

Eftersom det förblir oklart antar jag att endast ovanstående är givet.
Närnäst förenklade jag din fråga för att komma fram till:

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;

Nu, även om det hela är snyggt och glänsande att använda ren SQL, kommer detta att vara mycket snabbare med en plpgsql-funktion, eftersom den kan göra det i en enda tabellsökning där denna fråga behöver minst tre skanningar.

Snabbare med plpgsql-funktion:

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$;

Ring:

SELECT * FROM x.f_repeat_ct();

Se till att alltid tabellkvalificera dina kolumnnamn i den här typen av plpgsql-funktion, eftersom vi använder samma namn som utdataparametrar som skulle ha företräde om de inte är kvalificerade.

Miljarder rader

Om du har miljarder rader , kanske du vill dela upp den här operationen. Jag citerar manualen här:

Obs:Den nuvarande implementeringen av RETURN NEXT och RETURN QUERY lagrar hela resultatuppsättningen innan den återgår från funktionen, som diskuterats ovan. Det betyder att om en PL/pgSQL-funktion ger en mycket stor resultatuppsättning, kan prestandan vara dålig:data kommer att skrivas till disken för att undvika minnesutmattning, men själva funktionen kommer inte att återgå förrän hela resultatuppsättningen har genererats. En framtida version av PL/pgSQL kan tillåta användare att definiera set-retur-funktioner som inte har denna begränsning. För närvarande styrs punkten då data börjar skrivas till disken av variabeln work_memconfiguration. Administratörer som har tillräckligt med minne för att lagra större resultatuppsättningar i minnet bör överväga att öka denna parameter.

Överväg att beräkna rader för ett system åt gången eller ange ett tillräckligt högt värde för work_mem för att klara belastningen. Följ länken i citatet om mer om work_mem.

Ett sätt skulle vara att ställa in ett mycket högt värde för work_mem med SET LOCAL i din funktion, som endast gäller för den aktuella transaktionen. Jag lade till en kommenterad rad i funktionen. Gör inte sätt den mycket högt globalt, eftersom detta kan skada din server. Läs manualen.




  1. Varför kan jag skapa en tabell med PRIMARY KEY på en nullbar kolumn?

  2. Vilken typ av JOIN som ska användas

  3. Hur applicerar man en funktion på varje element i en matriskolumn i Postgres?

  4. InMemory DUBLIKAT Förvirring i Oracle RAC