4

このようなテーブルがあります。

ID (integer)
event_name(varchar(20))
event_date(timestamp)

いくつかのサンプル データを以下に示します。

ID         event_date                          event_name
101        2013-04-24 18:33:37.694818          event_A
102        2013-04-24 20:34:37.000000          event_B
103        2013-04-24 20:40:37.000000          event_A
104        2013-04-25 01:00:00.694818          event_A
105        2013-04-25 12:00:15.694818          event_A
106        2013-04-26 00:56:10.800000          event_A
107        2013-04-27 12:00:15.694818          event_A
108        2013-04-27 12:00:15.694818          event_B

ウィンドウ ベースのレポートを生成する必要があります。ここで、ウィンドウは行のグループを表します。例: ウィンドウ サイズ 2 を選択した場合、2 日間 (つまり、同じ日と前日) の各イベントの合計数を表示する必要があります。ウィンドウ サイズ 3 を選択した場合、連続する 3 日間の各イベントのカウントを生成する必要があります。

そのため、2 日間のウィンドウが選択されている場合、結果は次のようになります。

Date                                       Count_eventA                 Count_eventB
2013-04-27 (this counts sum of 27th, 26th)       2                           1 
2013-04-26 (this counts sum of 26th, 25th)       3                           0
2013-04-25 (this counts sum of 25th, 24th)       4                           1
2013-04-24 (this counts sum of 24th      )       2                           1

postgres でウィンドウ関数を読みました。このレポートの SQL クエリの書き方を誰か教えてくれませんか!

4

1 に答える 1

6

count集計をウィンドウ関数として使用したい場合count(id) over (partition by event_date rows 3 preceeding)などがありますが、データの性質上非常に複雑です。日付だけでなくタイムスタンプを保存していて、以前のイベントの数ではなくごとにグループ化したい。さらに、結果をクロス集計する必要があります。

PostgreSQLRANGEがウィンドウ関数でサポートされている場合、これは実際よりもかなり単純になります。そのままでは、難しい方法で行う必要があります。

次に、ウィンドウを介してそれをフィルター処理して、イベントごとの 1 日ごとのラグカウントを取得できます...ただし、イベント日は連続しておらず、残念ながら PostgreSQL ウィンドウ関数は のみをサポートし、 はサポートROWSしないRANGEため、生成されたシリーズ全体に参加する必要があります最初の日付の。

WITH
/* First, get a listing of event counts by day */
event_days(event_name, event_day, event_day_count) AS (
        SELECT event_name, date_trunc('day', event_date), count(id)
        FROM Table1
        GROUP BY event_name, date_trunc('day', event_date)
        ORDER BY date_trunc('day', event_date), event_name
),
/* 
 * Then fill in zeros for any days within the range that didn't have any events.
 * If PostgreSQL supported RANGE windows, not just ROWS, we could get rid of this/
 */
event_days_contiguous(event_name, event_day, event_day_count) AS (
        SELECT event_names.event_name, gen_day, COALESCE(event_days.event_day_count,0)
        FROM generate_series( (SELECT min(event_day)::date FROM event_days), (SELECT max(event_day)::date FROM event_days), INTERVAL '1' DAY ) gen_day
        CROSS JOIN (SELECT DISTINCT event_name FROM event_days) event_names(event_name)
        LEFT OUTER JOIN event_days ON (gen_day = event_days.event_day AND event_names.event_name = event_days.event_name)
),
/*
 * Get the lagged counts by using the sum() function over a row window...
 */
lagged_days(event_name, event_day_first, event_day_last, event_days_count) AS (
        SELECT event_name, event_day, first_value(event_day) OVER w, sum(event_day_count) OVER w
        FROM event_days_contiguous
        WINDOW w AS (PARTITION BY event_name ORDER BY event_day ROWS 1 PRECEDING)
)
/* Now do a manual pivot. For arbitrary column counts use an external tool
 * or check out the 'crosstab' function in the 'tablefunc' contrib module 
 */
SELECT d1.event_day_first, d1.event_days_count AS "Event_A", d2.event_days_count AS "Event_B"
FROM lagged_days d1
INNER JOIN lagged_days d2 ON (d1.event_day_first = d2.event_day_first AND d1.event_name = 'event_A' AND d2.event_name = 'event_B')
ORDER BY d1.event_day_first;

サンプル データを出力します。

    event_day_first     | Event_A | Event_B 
------------------------+---------+---------
 2013-04-24 00:00:00+08 |       2 |       1
 2013-04-25 00:00:00+08 |       4 |       1
 2013-04-26 00:00:00+08 |       3 |       0
 2013-04-27 00:00:00+08 |       2 |       1
(4 rows)

FROM (SELECT...)外側のクエリから使用する CTE の代わりに、3 つの CTE 句を組み合わせてネストされたクエリを使用し、それらをビューにラップすることで、クエリを高速化できますが、はるかに醜いものにすることができます。これにより、Pg は述語をクエリに「プッシュ ダウン」できるようになり、データのサブセットをクエリするときに使用する必要のあるデータが大幅に削減されます。

SQLFiddle は現時点では動作していないようですが、私が使用したデモのセットアップは次のとおりです。

CREATE TABLE Table1 
(id integer primary key, "event_date" timestamp not null, "event_name" text);

INSERT INTO Table1
("id", "event_date", "event_name")
VALUES
(101, '2013-04-24 18:33:37', 'event_A'),
(102, '2013-04-24 20:34:37', 'event_B'),
(103, '2013-04-24 20:40:37', 'event_A'),
(104, '2013-04-25 01:00:00', 'event_A'),
(105, '2013-04-25 12:00:15', 'event_A'),
(106, '2013-04-26 00:56:10', 'event_A'),
(107, '2013-04-27 12:00:15', 'event_A'),
(108, '2013-04-27 12:00:15', 'event_B');

最後のエントリの ID を 107 から 108 に変更しました。これは手動編集の単なるエラーだと思われます。

代わりにビューとして表現する方法は次のとおりです。

CREATE VIEW lagged_days AS
SELECT event_name, event_day AS event_day_first, sum(event_day_count) OVER w AS event_days_count 
FROM (
        SELECT event_names.event_name, gen_day, COALESCE(event_days.event_day_count,0)
        FROM generate_series( (SELECT min(event_date)::date FROM Table1), (SELECT max(event_date)::date FROM Table1), INTERVAL '1' DAY ) gen_day
        CROSS JOIN (SELECT DISTINCT event_name FROM Table1) event_names(event_name)
        LEFT OUTER JOIN (
                SELECT event_name, date_trunc('day', event_date), count(id)
                FROM Table1
                GROUP BY event_name, date_trunc('day', event_date)
                ORDER BY date_trunc('day', event_date), event_name
        ) event_days(event_name, event_day, event_day_count)
        ON (gen_day = event_days.event_day AND event_names.event_name = event_days.event_name)
) event_days_contiguous(event_name, event_day, event_day_count)
WINDOW w AS (PARTITION BY event_name ORDER BY event_day ROWS 1 PRECEDING);

その後、記述したい任意のクロス集計クエリでビューを使用できます。前のハンド クロス集計クエリで動作します。

SELECT d1.event_day_first, d1.event_days_count AS "Event_A", d2.event_days_count AS "Event_B"
FROM lagged_days d1
INNER JOIN lagged_days d2 ON (d1.event_day_first = d2.event_day_first AND d1.event_name = 'event_A' AND d2.event_name = 'event_B')
ORDER BY d1.event_day_first;

...または拡張機能crosstabから使用しtablefuncます。これについては、学習させてください。

笑いのためexplainに、これは上記のビューベースのクエリです: http://explain.depesz.com/s/nvUq

于 2013-06-10T05:12:01.053 に答える