7

ログのような製品の使用状況を表すテーブルがあります。製品の使用状況は複数のタイムスタンプとして記録されます。時間範囲を使用して同じデータを表現したいと思います。

次のようになります(PostgreSQL 9.1):

userid | timestamp          | product
-------------------------------------
001    | 2012-04-23 9:12:05 | foo
001    | 2012-04-23 9:12:07 | foo
001    | 2012-04-23 9:12:09 | foo
001    | 2012-04-23 9:12:11 | barbaz
001    | 2012-04-23 9:12:13 | barbaz
001    | 2012-04-23 9:15:00 | barbaz
001    | 2012-04-23 9:15:01 | barbaz
002    | 2012-04-24 3:41:01 | foo
002    | 2012-04-24 3:41:03 | foo

前回の実行との時間差がデルタ(たとえば2秒)未満の行を折りたたんで、次のように開始時刻と終了時刻を取得したいと思います。

userid | begin              | end                | product
----------------------------------------------------------
001    | 2012-04-23 9:12:05 | 2012-04-23 9:12:09 | foo
001    | 2012-04-23 9:12:11 | 2012-04-23 9:12:13 | barbaz
001    | 2012-04-23 9:15:00 | 2012-04-23 9:15:01 | barbaz
002    | 2012-04-24 3:41:01 | 2012-04-24 3:41:03 | foo

同じ製品の連続使用は、使用量がデルタ(この例では2秒)以上離れている場合、2つの行に分割されることに注意してください。

create table t (userid int, timestamp timestamp, product text);

insert into t (userid, timestamp, product) values 
(001, '2012-04-23 9:12:05', 'foo'),
(001, '2012-04-23 9:12:07', 'foo'),
(001, '2012-04-23 9:12:09', 'foo'),
(001, '2012-04-23 9:12:11', 'barbaz'),
(001, '2012-04-23 9:12:13', 'barbaz'),
(001, '2012-04-23 9:15:00', 'barbaz'),
(001, '2012-04-23 9:15:01', 'barbaz'),
(002, '2012-04-24 3:41:01', 'foo'),
(002, '2012-04-24 3:41:03', 'foo')
;
4

1 に答える 1

9

@a_horse_with_no_name によってしばらく前に与えられたこの回答に触発されました。

WITH groupped_t AS (
SELECT *, sum(grp_id) OVER (ORDER BY userid,product,"timestamp") AS grp_nr
  FROM (SELECT t.*,
          lag("timestamp") OVER
           (PARTITION BY userid,product ORDER BY "timestamp") AS prev_ts,
          CASE WHEN ("timestamp" - lag("timestamp") OVER
            (PARTITION BY userid,product ORDER BY "timestamp")) <= '2s'::interval
          THEN NULL ELSE 1 END AS grp_id
        FROM t) AS g
), periods AS (
SELECT min(gt."timestamp") AS grp_min, max(gt."timestamp") AS grp_max, grp_nr
  FROM groupped_t AS gt
 GROUP BY gt.grp_nr
)
SELECT gt.userid, p.grp_min AS "begin", p.grp_max AS "end", gt.product
  FROM periods p
  JOIN groupped_t gt ON gt.grp_nr = p.grp_nr AND gt."timestamp" = p.grp_min
 ORDER BY gt.userid, p.grp_min;
  1. userid最も内側のクエリは、product時間差に基づいてグループ化 ID を割り当てます。PARTITION BY実際、最初の 2 つのフィールドは安全だと思いました。
  2. groupped_tすべてのソース列 + 追加のランニング グループ番号が表示されます。グループ ID を一意にする必要があるORDER BYため、ここではウィンドウ関数のみを使用しました。sum()
  3. periods各グループの最初と最後のタイムスタンプの単なるヘルパー クエリです。
  4. 最後に、各グループの最初のエントリのタイムスタンプ (一意にする必要があるのはそのためです) と結合groupped_tします。periodsgrp_nr

SQL Fiddleでこのクエリを確認することもできます。

とは SQL ( PostgreSQLの場合も) の予約語であるためtimestamp、避けるか二重引用符で囲む必要があります。beginendend

于 2012-06-25T15:33:37.043 に答える