Przypadek testowy
Po pierwsze, bardziej użyteczny sposób prezentowania danych – lub jeszcze lepiej, w sqlfiddle , gotowy do gry z:
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);
Uproszczone zapytanie
Ponieważ pozostaje to niejasne, zakładam, że podano tylko powyższe.
Następnie uprościłem Twoje zapytanie, aby dotrzeć do:
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;
Teraz, chociaż korzystanie z czystego SQL jest ładne i błyszczące, będzie to dużo szybciej z funkcją plpgsql, ponieważ może to zrobić w pojedynczym skanowaniu tabeli, gdzie to zapytanie wymaga co najmniej trzech skanów.
Szybciej dzięki funkcji 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$;
Zadzwoń:
SELECT * FROM x.f_repeat_ct();
Pamiętaj, aby przez cały czas zakwalifikować nazwy kolumn do tabeli w tego rodzaju funkcji plpgsql, ponieważ używamy tych samych nazw jako parametrów wyjściowych, które mają pierwszeństwo, jeśli nie są kwalifikowane.
Miliardy wierszy
Jeśli masz miliardy wierszy , możesz podzielić tę operację. Cytuję instrukcję tutaj:
Uwaga:aktualna implementacja RETURN NEXT
i RETURN QUERY
przechowuje cały zestaw wyników przed powrotem z funkcji, jak omówiono powyżej. Oznacza to, że jeśli funkcja PL/pgSQL generuje bardzo duży zestaw wyników, wydajność może być słaba:dane zostaną zapisane na dysku, aby uniknąć wyczerpania pamięci, ale sama funkcja nie powróci, dopóki nie zostanie wygenerowany cały zestaw wyników. Przyszła wersja PL/pgSQL może umożliwiać użytkownikom definiowanie funkcji zwracania zestawu, które nie mają tego ograniczenia. Obecnie punkt, w którym rozpoczyna się zapis danych na dysku, jest kontrolowany przez zmienną work_memconfiguration. Administratorzy, którzy mają wystarczającą ilość pamięci, aby przechowywać w pamięci większe zestawy wyników, powinni rozważyć zwiększenie tego parametru.
Rozważ obliczanie wierszy dla jednego systemu na raz lub ustaw wystarczająco wysoką wartość dla work_mem
radzić sobie z obciążeniem. Kliknij link podany w cytacie, aby dowiedzieć się więcej o work_mem.
Jednym ze sposobów byłoby ustawienie bardzo wysokiej wartości dla work_mem
z SET LOCAL
w swojej funkcji, która obowiązuje tylko dla bieżącej transakcji. Dodałem skomentowaną linię w funkcji. Nie ustaw go bardzo wysoko globalnie, ponieważ może to zniszczyć twój serwer. Przeczytaj instrukcję.