web-dev-qa-db-ja.com

グループ化されたタイムスタンプの最小値と最大値を取得する方法

タイムスタンプを10分間隔で最も近い10分間にグループ化し、それぞれの最小および最大のタイムスタンプを含むビューを作成する方法を知りたいです。

したがって、次のようなテーブルです。

| Hero         | timestamp           |

| Batman       | 2016-12-08 12:00:00 |
| Batman       | 2016-12-08 12:07:00 |
| Batman       | 2016-12-08 13:00:00 |
| Batman       | 2016-12-08 14:00:00 |
| Wonder Woman | 2016-12-08 10:15:00 |
| Wonder Woman | 2016-12-08 10:18:00 |
| Wonder Woman | 2016-12-08 10:25:00 |
| Wonder Woman | 2016-12-08 10:30:00 |

このようなビューになります

| Hero         | start_time          | end_time            |

| Batman       | 2016-12-08 12:00:00 | 2016-12-08 12:07:00 |
| Wonder Woman | 2016-12-08 10:15:00 | 2016-12-08 10:30:00 |

またはこれ:

| Hero         | start_time          | end_time            |

| Batman       | 2016-12-08 13:00:00 | NULL                |
| Batman       | 2016-12-08 14:00:00 | NULL                |
| Batman       | 2016-12-08 12:00:00 | 2016-12-08 12:07:00 |
| Wonder Woman | 2016-12-08 10:15:00 | 2016-12-08 10:30:00|

どちらのソリューションでも問題ありません。

5
alexanderadam

10分間隔のラスター

「時間」と10分間隔の組み合わせでグループ化することをお勧めします。

_SELECT hero
     , min(timestamp) AS start_time
     , CASE WHEN count(*) > 1 THEN max(timestamp) END AS end_time
FROM   tbl
GROUP  BY hero
     , date_trunc('hour', timestamp)
     , EXTRACT(MINUTE FROM timestamp)::int / 10
ORDER  BY 1, 2;  -- optional
_

日付/時刻関数を使用します。

EXTRACT(minute FROM timestamp)は、時間の分の部分を抽出する式です。整数(_::int_)へのキャスト後、整数除算(_/ 10_)は、実質的に10分間隔に丸められます(_0_-_5_)。

CASE式は、同じ10分の間隔に複数の行が含まれる場合にのみ_end_time_を追加します。

「タイムスタンプ」を識別子として使用しないことをお勧めします。これは、標準SQLでは予約語であり、Postgresでは基本データ型です。

10分以上のギャップで定義されたグループ

「グループ」が同じヒーローの列間の10分以上のギャップによって定義されている場合:

_SELECT hero
     , count(*) AS ct  -- optional
     , min(timestamp) AS start_time
     , CASE WHEN count(*) > 1 THEN max(timestamp) END AS end_time
FROM  (
   SELECT hero, timestamp, count(step OR NULL) OVER (ORDER BY hero, timestamp) AS grp
   FROM  (
      SELECT *
           , lag(timestamp) OVER (PARTITION BY hero ORDER BY timestamp)
           < timestamp - interval '10 min' AS step
      FROM   tbl
      ) sub1
   ) sub2
GROUP  BY hero, grp;
_

詳細な説明:

SQL Fiddle 両方の場合。

3

時間間隔を丸める代わりに整数を丸めたい場合は、「最も近い1ダース」にすると、次の操作を実行します。

number::integer / 12 * 12 

12で除算したり乗算したりすると、「何もしない」ように見えますが、実際には、数値の「ダースの1ダース」は取り除かれます。秘訣は、除算が整数除算であり、結果が小数部分のない整数であるという事実にあります。

たとえば、試してみてください:

SELECT
     1 / 12 * 12 AS n1,
     2 / 12 * 12 AS n2,
    11 / 12 * 12 AS n11,
    12 / 12 * 12 AS n12,
    13 / 12 * 12 AS n13,
    23 / 12 * 12 AS n23,
    24 / 12 * 12 AS n24,
    25 / 12 * 12 AS n25,
    35 / 12 * 12 AS n35 ;

これは、特定のビンサイズ(この場合は12)を指定して、数値を「切り捨て」てビンに入れるための標準的な方法です。

タイムスタンプを整数(またはbigints)に変換するのと同じように、時間についても同じことができます。そのためには、タイムスタンプを「特定の日時からの秒数」として表現します。タイムスタンプの " Epoch "は、特定の日時(1970年1月1日00:00:00)から経過した秒数を表します。 PostgreSQL(そして一般的にはSQLだと思います)は、この値を取得するために「EXTRACT(Epoch FROM ts)」を使用します。

これらのエポックを任意の間隔に丸めたい場合は(この場合は10分= 60 * 10秒)、このエポックを整数で600で除算し(小数を取り除く)、この600で乗算します。このようにして、10分の間隔の「端数部分」を取り除きます。

次のコードは、考えられるソリューションの2番目のバージョンを提供します(ワンダーウーマン用にいくつかの追加の行があります)。

CREATE TABLE hero_stamps (hero text, ts timestamp without time zone) ;

INSERT INTO 
    hero_stamps (hero, ts)
VALUES
    ('Batman', '2016-12-08 12:00:00'),
    ('Batman', '2016-12-08 12:07:00'),
    ('Batman', '2016-12-08 13:00:00'),
    ('Batman', '2016-12-08 14:00:00'),
    ('Wonder Woman', '2016-12-08 10:15:00'),
    ('Wonder Woman', '2016-12-08 10:18:00'),
    ('Wonder Woman', '2016-12-08 10:25:00'),
    ('Wonder Woman', '2016-12-08 11:30:00') ;

SELECT
    hero, 
    start_time, 
    (CASE WHEN start_time = end_time THEN NULL ELSE end_time END) AS end_time
FROM
(
    SELECT
        hero, 
        extract(Epoch from ts)::bigint   /* timestamp converted to Epoch */
           / (60 * 10)::integer          /* integer div */
           * (60 * 10)::integer          /* multiply back to have start time */
        AS Epoch_start_time_of_interval,
        min(ts) AS start_time, 
        max(ts) AS end_time 
    FROM
        hero_stamps
    GROUP BY
        hero, Epoch_start_time_of_interval
) AS s0
ORDER BY
    hero, start_time ;

GROUP BYは「ビニング」を行い、min(ts)とmax(ts)は最大値と最小値を提供し、最も外側のSELECTの追加関数はnullを希望どおりに提供します(そして「Epoch_start_time_of_interval」を取り除きます「あなたが興味を持たないこと)。

この方法は、「時間」と「分」が同じでカレンダーの日付が異なるタイムスタンプがある場合でも機能します。これは、この情報が無視されないためです(「エポック」に埋め込まれます)。

よりコンパクトなバージョン(同じ結果が得られる)は

SELECT
    hero, 
    min(ts) AS start_time, 
    (CASE WHEN count(*) = 1 THEN NULL ELSE max(ts) END) AS end_time 
FROM
    hero_stamps
GROUP BY
    hero, 
    extract(Epoch from ts)::bigint / 600
ORDER BY
    1, 2 ;
2
joanolo