Testcase
For det første en mere nyttig måde at præsentere dine data på - eller endnu bedre, i en sqlfiddle , klar til at lege 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);
Forenklet forespørgsel
Da det stadig er uklart, antager jeg kun ovenstående som givet.
Dernæst har jeg forenklet din forespørgsel for at nå frem til:
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;
Selvom det hele er flot og skinnende at bruge ren SQL, vil det være meget hurtigere med en plpgsql-funktion, fordi den kan gøre det i en enkelt tabelscanning, hvor denne forespørgsel har brug for mindst tre scanninger.
Hurtigere 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 til:
SELECT * FROM x.f_repeat_ct();
Sørg for at tabelkvalificere dine kolonnenavne til enhver tid i denne form for plpgsql-funktion, fordi vi bruger de samme navne som outputparametre, som ville have forrang, hvis de ikke er kvalificerede.
Milliarder af rækker
Hvis du har milliarder rækker , vil du måske dele denne operation op. Jeg citerer manualen her:
Bemærk:Den aktuelle implementering af RETURN NEXT
og RETURN QUERY
gemmer hele resultatsættet, før det vender tilbage fra funktionen, som beskrevet ovenfor. Det betyder, at hvis en PL/pgSQL-funktion producerer et meget stort resultatsæt, kan ydeevnen være dårlig:data vil blive skrevet til disken for at undgå udmattelse af hukommelsen, men selve funktionen vender ikke tilbage, før hele resultatsættet er blevet genereret. En fremtidig version af PL/pgSQL kan tillade brugere at definere sæt-retur-funktioner, der ikke har denne begrænsning. I øjeblikket styres det punkt, hvor data begynder at blive skrevet til disken, af work_memconfiguration-variablen. Administratorer, der har tilstrækkelig hukommelse til at gemme større resultatsæt i hukommelsen, bør overveje at øge denne parameter.
Overvej at beregne rækker for et system ad gangen eller indstil en høj nok værdi for work_mem
at klare belastningen. Følg linket i citatet om mere om work_mem.
En måde ville være at indstille en meget høj værdi for work_mem
med SET LOCAL
i din funktion, som kun er effektiv for den aktuelle transaktion. Jeg tilføjede en kommentarlinje i funktionen. Gør ikke sæt det meget højt globalt, da dette kan ødelægge din server. Læs manualen.