2012-10-22 17 views
7

私は、2分の頻度で数週間の観測値を含むデータセットを持っています。 時間間隔を2分から5分に増やしたいと思います。問題は、観測の頻度が常に同じではないということです。理論的には、10分ごとに5回の観察が必要ですが、通常はそうではありません。 平均的な関数と観測時刻と日付に基づいて観測値を集計する方法を教えてください。 換言すると、5分毎の集計は、5分の時間間隔ごとに観測数が同じではない。 また、タイムスタンプ形式の日付と時刻があります。SQLの日付と時刻による集計の実行

例データ:

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 
+0

サンプルデータ:あなたが持っているもの、必要なもの。サンプルを簡単にテストできるように、insert文として記述します。また、どのデータベースブランドを使用しているかをお知らせください。 – danihp

+0

@danihpデータサンプル:[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:06 5.34、私はPostgreSQLを使用しています –

+2

@aliamidi - あなたは本当にそのような情報をコメントではなく、質問に入れるべきです。私があなたの質問にした編集を参照してください...また、あなたはその出力があなたが期待したものである理由を説明できますか?第二の記録はなぜ「23:06」ではなく「23:05」であるのですか?そして、期待される「5.34」はどこから来たのでしょうか? – MatBailie

答えて

6

this questionへの回答は、時間窓にデータを効率的に集約する方法を示す、おそらく問題の良い解決策を提供します。基本的に

、とavg集計を使用します。

GROUP BY floor(extract(epoch from the_timestamp)/60/5) 
1

最も簡単なオプションは、参照テーブルを作成することです。そのテーブルでは、あなたがinsterestedされ、その上の間隔格納します(。あなた自身のRDBMSの日付表記にこれを適応)

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 

注:間隔テーブルを一度作成して設定するだけで、何度でも再利用することができます。

+1

なぜ、 'insert ... select'を使って挿入が複雑になるのですか?単純な 'values'節ははるかにストレートです。 –

+0

@a_horse_with_no_nameに同意する傾向があります。 'insert ... select'はかなり奇妙です。 'VALUES( 'first'、 'row')、( 'second'、 'row');'リストははるかに明確で簡単です。しかし、 'generate_series'を使って数分で基本日付までの間隔を追加することができれば、値を手作業で作るのは変です。 –

2

編集:私はこれについて考えてもう少しやった、あなたはわずか5分に2分から行くことができないことに気づきました。それは合わない。私はそれをフォローアップしますが、集約するために1分のデータがあれば、次のコードが機能します!

-

データは「初め」フォーマットである場合は、この関数内のコードを使用し、またはアクセスを容易にするために、データベース上の関数を作成することができますちょうど

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 $1)/($2*60))*$2*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午前2時00分00秒」

ちょうどそれをテストしてbuilt-in timestamp functionsを使用してタイムスタンプを終了対始めて処理するための時間を加算または減算。

希望のタイムスタンプを取得したら、希望のaggregate functions(おそらく平均)と共に、そのタイムスタンプでCraigが言ったこととGROUP BYを実行します。あなたのインターバル期間は例えば揮発性である場合 - それはあなたがタイムスタンプを平均化したいということが判明するかもしれ

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 */ 

:あなたは/テストでそれを微調整することができ

。このために、フロアを取る代わりにタイムスタンプを丸める同様の関数を作ることができます。

1

これは単なる処理方法です。分析ニーズに合わせてデータをどのように変換するのか考えていただければ幸いです。

このコードをテストするための前提条件があります。すべての可能な1分のタイムスタンプを持つテーブルが必要です。これについては多くの方法がありますが、利用可能なものを使用します。これは、1分のテーブル(00:01:00)から(23:59:00)までのdim_timeと可能なすべてのテーブル日付(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 $1)/($2*60))*$2*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', $1) + cast(($2::varchar||' min') as interval) * round(date_part('minute',$1)::float/cast($2 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