27

私のアプリには、Eventsタイムスタンプ付きのイベントを含むテーブルがあります。

最新の各N時間間隔でのイベントの数を報告する必要があります。さまざまなレポートの場合、間隔は「毎週」、「毎日」、「毎時」、「15分間隔」のいずれかになります。

たとえば、ユーザーは、毎週、1日、1時間、または15分ごとに受け取った注文の数を表示できます。

1)私の好みは、任意の時間間隔でグループ化する単一のSQLクエリ(Postgresを使用しています)を動的に実行することです。それを行う方法はありますか?

2)簡単ですが醜いブルートフォース攻撃の方法は、タイムスタンプでソートされた開始/終了時間枠内のすべてのレコードに対して単一のクエリを実行し、メソッドに任意の間隔で手動で集計を作成させることです。

3)別のアプローチは、間隔ごとにイベントテーブルに個別のフィールドを追加し、、、、およびフィールドを静的the_week the_dayに格納することです。そのため、そのフィールドについてレポートするたびにではなく、レコードが作成されたときに(1回)「ヒット」を取得します。 。the_hourthe_quarter_hour

必要に応じてモデルを変更し、間隔データを事前に保存できるとすると、ここでのベストプラクティスは何ですか(ただし、テーブルの幅を2倍にするというわずかな費用がかかります)。

4

1 に答える 1

51

幸い、PostgreSQLを使用しています。ウィンドウ関数generate_series()はあなたの友達です。

テストケース

次のテストテーブル(提供する必要があります)を前提としています。

CREATE TABLE event(event_id serial, ts timestamp);
INSERT INTO event (ts)
SELECT generate_series(timestamp '2018-05-01'
                     , timestamp '2018-05-08'
                     , interval '7 min') + random() * interval '7 min';

7分ごとに1つのイベント(さらに0〜7分、ランダムに)。

基本的な解決策

このクエリは、任意の時間間隔のイベントをカウントします。例では17分:

WITH grid AS (
   SELECT start_time
        , lead(start_time, 1, 'infinity') OVER (ORDER BY start_time) AS end_time
   FROM  (
      SELECT generate_series(min(ts), max(ts), interval '17 min') AS start_time
      FROM   event
      ) sub
   )
SELECT start_time, count(e.ts) AS events
FROM   grid       g
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.end_time
GROUP  BY start_time
ORDER  BY start_time;

tsクエリは、完全な時間範囲をカバーするために、ベーステーブルから最小値と最大値を取得します。代わりに、任意の時間範囲を使用できます。

必要に応じて任意の時間間隔を指定します。

タイムスロットごとに1つの行を生成します。その間隔中にイベントが発生しなかった場合、カウントは0です。

上限と下限を正しく処理するようにしてください。見る:

ウィンドウ関数lead()には、見過ごされがちな機能があります。先頭の行が存在しない場合のデフォルトを提供できます。例で提供'infinity'します。それ以外の場合、最後の間隔は上限で切り捨てられNULLます。

最小限の同等物

上記のクエリは、CTElead()および冗長構文を使用しています。エレガントで理解しやすいかもしれませんが、少し高価です。これは、より短く、より速く、最小限のバージョンです。

SELECT start_time, count(e.ts) AS events
FROM  (SELECT generate_series(min(ts), max(ts), interval '17 min') FROM event) g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '17 min'
GROUP  BY 1
ORDER  BY 1;

「先週15分ごと」の例`

でフォーマットされていto_char()ます。

SELECT to_char(start_time, 'YYYY-MM-DD HH24:MI'), count(e.ts) AS events
FROM   generate_series(date_trunc('day', localtimestamp - interval '7 days')
                     , localtimestamp
                     , interval '15 min') g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '15 min'
GROUP  BY start_time
ORDER  BY start_time;

それでもORDER BY、フォーマットされた文字列ではなくGROUP BY、基になるタイムスタンプ値にあります。これは、より高速で信頼性が高くなります。

ここでdb<>フィドル

時間枠全体の実行カウントを生成する関連する回答:

于 2013-03-22T18:28:31.327 に答える