Скользящее среднее на основе временных меток в PostgreSQL
Я хотел выполнить скользящее среднее через отметки времени.
У меня есть две колонки: температура и временные метки (дата-время), и я хочу выполнить скользящее среднее на основе каждых 15 минут последовательных наблюдений за температурой. Другими словами, выбор данных для выполнения среднего значения на основе 15-минутного интервала времени. Кроме того, возможно различное количество наблюдений для разных временных последовательностей. Я имел в виду, что все размеры окон равны (15 минут), но в каждом окне возможно различное количество наблюдений.
Например:
Для первого окна мы должны вычислить среднее значение n наблюдения, а для второго окна вычислить среднее значение наблюдения для наблюдения n + 5.
Пример данных:
ID Timestamps Temperature
1 2007-09-14 22:56:12 5.39
2 2007-09-14 22:58:12 5.34
3 2007-09-14 23:00:12 5.16
4 2007-09-14 23:02:12 5.54
5 2007-09-14 23:04:12 5.30
6 2007-09-14 23:06:12 5.20
7 2007-09-14 23:10:12 5.39
8 2007-09-14 23:12:12 5.34
9 2007-09-14 23:20:12 5.16
10 2007-09-14 23:24:12 5.54
11 2007-09-14 23:30:12 5.30
12 2007-09-14 23:33:12 5.20
13 2007-09-14 23:40:12 5.39
14 2007-09-14 23:42:12 5.34
15 2007-09-14 23:44:12 5.16
16 2007-09-14 23:50:12 5.54
17 2007-09-14 23:52:12 5.30
18 2007-09-14 23:57:12 5.20
Основные проблемы:
Как я могу узнать код, чтобы различать каждые 15 минут, пока нет точных 15-минутных интервалов времени из-за различной частоты дискретизации.
Ответы
Ответ 1
Предполагая, что вы хотите перезапустить скользящее среднее после каждого 15-минутного интервала:
select id,
temp,
avg(temp) over (partition by group_nr order by time_read) as rolling_avg
from (
select id,
temp,
time_read,
interval_group,
id - row_number() over (partition by interval_group order by time_read) as group_nr
from (
select id,
time_read,
'epoch'::timestamp + '900 seconds'::interval * (extract(epoch from time_read)::int4 / 900) as interval_group,
temp
from readings
) t1
) t2
order by time_read;
Он основан на Depesz solution для группировки по "диапазонам времени":
Вот пример SQLFiddle: http://sqlfiddle.com/#!1/0f3f0/2
Ответ 2
Вы можете присоединиться к своей таблице с самим собой:
select l1.id, avg( l2.Temperature )
from l l1
inner join l l2
on l2.id <= l1.id and
l2.Timestamps + interval '15 minutes' > l1.Timestamps
group by l1.id
order by id
;
Результаты:
| ID | AVG |
-----------------------
| 1 | 5.39 |
| 2 | 5.365 |
| 3 | 5.296666666667 |
| 4 | 5.3575 |
| 5 | 5.346 |
| 6 | 5.321666666667 |
| 7 | 5.331428571429 |
Примечание. Выполняется только "тяжелая работа". Вы должны присоединиться к результату с исходной таблицей или добавить новые столбцы для запроса. Я не знаю, что нужно для вашего последнего запроса. Адаптируйте это решение или попросите дополнительную помощь.
Ответ 3
Здесь используется подход, который использует средство для использования функции агрегации в качестве функции окна. Функция агрегата сохраняет последние 15 минут наблюдений в массиве вместе с текущим итогом. Функция перехода состояния смещает элементы из массива, которые отстают от 15-минутного окна, и нажимает на последнее наблюдение. Конечная функция просто вычисляет среднюю температуру в массиве.
Теперь, относительно того, выгодно это или нет... это зависит. Он фокусируется на части выполнения plgpsql в postgresql, а не на части доступа к базе данных, и мой собственный опыт заключается в том, что plpgsql не работает быстро. Если вы можете легко выполнить поиск в таблице, чтобы найти предыдущие 15-минутные строки для каждого наблюдения, самосоединение (как в ответе @danihp) будет хорошо. Однако этот подход может касаться наблюдений, происходящих из более сложного источника, где эти поиски непрактичны. Как всегда, проб и сравнений в вашей собственной системе.
-- based on using this table definition
create table observation(id int primary key, timestamps timestamp not null unique,
temperature numeric(5,2) not null);
-- note that I'm reusing the table structure as a type for the state here
create type rollavg_state as (memory observation[], total numeric(5,2));
create function rollavg_func(state rollavg_state, next_in observation) returns rollavg_state immutable language plpgsql as $$
declare
cutoff timestamp;
i int;
updated_memory observation[];
begin
raise debug 'rollavg_func: state=%, next_in=%', state, next_in;
cutoff := next_in.timestamps - '15 minutes'::interval;
i := array_lower(state.memory, 1);
raise debug 'cutoff is %', cutoff;
while i <= array_upper(state.memory, 1) and state.memory[i].timestamps < cutoff loop
raise debug 'shifting %', state.memory[i].timestamps;
i := i + 1;
state.total := state.total - state.memory[i].temperature;
end loop;
state.memory := array_append(state.memory[i:array_upper(state.memory, 1)], next_in);
state.total := coalesce(state.total, 0) + next_in.temperature;
return state;
end
$$;
create function rollavg_output(state rollavg_state) returns float8 immutable language plpgsql as $$
begin
raise debug 'rollavg_output: state=% len=%', state, array_length(state.memory, 1);
if array_length(state.memory, 1) > 0 then
return state.total / array_length(state.memory, 1);
else
return null;
end if;
end
$$;
create aggregate rollavg(observation) (sfunc = rollavg_func, finalfunc = rollavg_output, stype = rollavg_state);
-- referring to just a table name means a tuple value of the row as a whole, whose type is the table type
-- the aggregate relies on inputs arriving in ascending timestamp order
select rollavg(observation) over (order by timestamps) from observation;