Выполнение агрегирования по дате и времени в SQL
у меня есть набор данных, который содержит наблюдения в течение нескольких недель с частотой 2 минут. Я хочу увеличить временной интервал с 2 минут до 5 минут. Проблема в том, что частота наблюдений не всегда совпадают. Я имею в виду, теоретически, каждые 10 минут должно быть 5 наблюдений, но обычно это не так. Пожалуйста, дайте мне знать, как я могу агрегировать наблюдения на основе средней функции и в отношении времени и даты наблюдений. В других агрегация слов на основе каждые 5 минут, в то время как количество наблюдений не одинаковы для каждого 5-минутного интервала времени. Кроме того, у меня есть дата и время в формате timestamp.
Пример:
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
ожидаемые результаты:
1 2007-09-14 23:00 5.29
2 2007-09-14 23:05 5.34
4 ответов
ответы этот вопрос вероятно, обеспечивают хорошие решения вашей проблемы, показывая способы эффективного агрегирования данных в окна времени.
по сути, используйте avg
совокупности с:
GROUP BY floor(extract(epoch from the_timestamp) / 60 / 5)
EDIT: я немного больше думал об этом и понял, что вы не можете просто перейти от 2-мин до 5-мин. Это не сходится. Я буду следить за этим, но следующий код работает, как только у вас есть данные 1-min для агрегирования!
--
если данные находятся в формате "начала", вы можете использовать код внутри этой функции или создать функцию в своей базе данных для удобства доступа:
CREATE OR REPLACE FUNCTION dev.beginning_datetime_floor(timestamp without time zone,
integer) /* switch out 'dev' with your schema name */
RETURNS timestamp without time zone AS
$BODY$
SELECT
date_trunc('minute',timestamp with time zone 'epoch' +
floor(extract(epoch from )/(*60))**60
* interval '1 second') at time zone 'CST6CDT' /* change this to your time zone */
$BODY$
LANGUAGE sql VOLATILE;
вы просто кормите его целым числом минут, которые вы хотите совокупности (использовать 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, или 30), вот результаты:
select dev.beginning_datetime_floor('2012-01-01 02:02:21',2)
= '2012-01-01 02:02:00'
select dev.beginning_datetime_floor('2012-01-01 02:02:21',5)
= '2012-01-01 02:00:00'
просто проверьте его и добавить или вычесть время для обработки начала и окончания метки времени с помощью встроенные функции метки времени.
когда вы получите метку времени, которую хотите, сделайте то, что сказал Крейг, и группируйте по этой метке времени в сочетании с желаемым агрегатные функции (скорее средние).
вы можете проверить/настроить его с:
date_trunc('minute',timestamp with time zone 'epoch' +
floor(extract(epoch from your_datetime)/(interval_minutes*60))*interval_minutes*60
* interval '1 second') at time zone 'CST6CDT' /* change this to your time zone */
может оказаться, что вы хотите усреднить временные метки - например, если продолжительность вашего интервала изменчива. Для этого вы можете сделать аналогичную функцию, которая округляет временную метку вместо того, чтобы брать пол.
на сегодняшний день самый простой вариант-создать справочную таблицу. В этой таблице вы храните интервалы, через которые вы insterested:
(приспособьте это к обозначению даты вашей собственной РСУБД.)
CREATE TABLE interval (
start_time DATETIME,
cease_time DATETIME
);
INSERT INTO interval SELECT '2012-10-22 12:00', '2012-10-22 12:05';
INSERT INTO interval SELECT '2012-10-22 12:05', '2012-10-22 12:10';
INSERT INTO interval SELECT '2012-10-22 12:10', '2012-10-22 12:15';
INSERT INTO interval SELECT '2012-10-22 12:15', '2012-10-22 12:20';
INSERT INTO interval SELECT '2012-10-22 12:20', '2012-10-22 12:25';
INSERT INTO interval SELECT '2012-10-22 12:25', '2012-10-22 12:30';
INSERT INTO interval SELECT '2012-10-22 12:30', '2012-10-22 12:35';
INSERT INTO interval SELECT '2012-10-22 12:35', '2012-10-22 12:40';
затем вы просто присоединяетесь и объединяетесь...
SELECT
interval.start_time,
AVG(observation.value)
FROM
interval
LEFT JOIN
observation
ON observation.timestamp >= interval.start_time
AND observation.timestamp < interval.cease_time
GROUP BY
interval.start_time
Примечание: вам нужно только создать и заполнить эту таблицу интервалов один раз, затем вы можете повторно использовать ее много раз.
Ok, так что это только один способ справиться с этим. Надеюсь, это заставит вас задуматься о том, как преобразовать данные для анализа.
есть необходимое условие для проверки этого кода. Вам нужно иметь таблицу со всеми возможными 1-минутными метками времени. Есть много способов сделать это, я просто использую то, что у меня есть, это одна таблица: dim_time, которая имеет каждую минуту (00:01:00) через (23:59:00) и другую таблицу со всеми возможными датами (dim_date). Когда вы присоединитесь к этим (1=1) Вы получаете все возможные минуты для всех возможных дней.
--first you need to create some functions I'll use later
--credit to this first function goes to David Walling
CREATE OR REPLACE FUNCTION dev.beginning_datetime_floor(timestamp without time zone, integer)
RETURNS timestamp without time zone AS
$BODY$
SELECT
date_trunc('minute',timestamp with time zone 'epoch' +
floor(extract(epoch from )/(*60))**60
* interval '1 second') at time zone 'CST6CDT'
$BODY$
LANGUAGE sql VOLATILE;
--the following function is what I described on my previous post
CREATE OR REPLACE FUNCTION dev.round_minutes(timestamp without time zone, integer)
RETURNS timestamp without time zone AS
$BODY$
SELECT date_trunc('hour', ) + cast((::varchar||' min') as interval) * round(date_part('minute',)::float / cast( as float))
$BODY$
LANGUAGE sql VOLATILE;
--let's load the data into a temp table, I added some data points. note: i got rid of the partial seconds
SELECT cast(timestamp_original as timestamp) as timestamp_original, datapoint INTO TEMPORARY TABLE timestamps_second2
FROM
(
SELECT '2007-09-14 22:56:12' as timestamp_original, 0 as datapoint
UNION
SELECT '2007-09-14 22:58:12' as timestamp_original, 1 as datapoint
UNION
SELECT '2007-09-14 23:00:12' as timestamp_original, 10 as datapoint
UNION
SELECT '2007-09-14 23:02:12' as timestamp_original, 100 as datapoint
UNION
SELECT '2007-09-14 23:04:12' as timestamp_original, 1000 as datapoint
UNION
SELECT '2007-09-14 23:06:12' as timestamp_original, 10000 as datapoint
) as data
--this is the bit of code you'll have to replace with your implementation of getting all possible minutes
--you could make some sequence of timestamps in R, or simply make the timestamps in Excel to test out the rest of the code
--the result of the query is simply '2007-09-14 00:00:00' through '2007-09-14 23:59:00'
SELECT * INTO TEMPORARY TABLE possible_timestamps
FROM
(
select the_date + beginning_minute as minute_timestamp
FROM datawarehouse.dim_date as dim_date
JOIN datawarehouse.dim_time as dim_time
ON 1=1
where dim_date.the_date = '2007-09-14'
group by the_date, beginning_minute
order by the_date, beginning_minute
) as data
--round to nearest minute (be sure to think about how this might change your results
SELECT * INTO TEMPORARY TABLE rounded_timestamps2
FROM
(
SELECT dev.round_minutes(timestamp_original,1) as minute_timestamp_rounded, datapoint
from timestamps_second2
) as data
--let's join what minutes we have data for versus the possible minutes
--I used some subqueries so when you select all from the table you'll see the important part (not needed)
SELECT * INTO TEMPORARY TABLE joined_with_possibles
FROM
(
SELECT *
FROM
(
SELECT *, (MIN(minute_timestamp_rounded) OVER ()) as min_time, (MAX(minute_timestamp_rounded) OVER ()) as max_time
FROM possible_timestamps as t1
LEFT JOIN rounded_timestamps2 as t2
ON t1.minute_timestamp = t2.minute_timestamp_rounded
ORDER BY t1.minute_timestamp asc
) as inner_query
WHERE minute_timestamp >= min_time
AND minute_timestamp <= max_time
) as data
--here's the tricky part that might not suit your needs, but it's one method
--if it's missing a value it grabs the previous value
--if it's missing the prior value it grabs the one before that, otherwise it's null
--best practice would be run another case statement with 0,1,2 specifying which point was pulled, then you can count those when you aggregate
SELECT * INTO TEMPORARY TABLE shifted_values
FROM
(
SELECT
*,
case
when datapoint is not null then datapoint
when datapoint is null and (lag(datapoint,1) over (order by minute_timestamp asc)) is not null
then lag(datapoint,1) over (order by minute_timestamp asc)
when datapoint is null and (lag(datapoint,1) over (order by minute_timestamp asc)) is null and (lag(datapoint,2) over (order by minute_timestamp asc)) is not null
then lag(datapoint,2) over (order by minute_timestamp asc)
else null end as last_good_value
from joined_with_possibles
ORDER BY minute_timestamp asc
) as data
--now we use the function from my previous post to make the timestamps to aggregate on
SELECT * INTO TEMPORARY TABLE shifted_values_with_five_minute
FROM
(
SELECT *, dev.beginning_datetime_floor(minute_timestamp,5) as five_minute_timestamp
FROM shifted_values
) as data
--finally we aggregate
SELECT
AVG(datapoint) as avg_datapoint, five_minute_timestamp
FROM shifted_values_with_five_minute
GROUP BY five_minute_timestamp