2013-06-10 1 views
2

У меня есть таблица, подобная этой.Как использовать «Группировать по» для интервала даты в postgres

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, мне нужно показать общий подсчет каждого события в течение двух дней подряд, то есть в тот же день и предыдущий день. Если я выбираю размер окна 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-запрос для этого отчета!

+0

Ваши ожидаемые результаты немного неправильно. Событие A для 25-го должно получиться 4, а не 3. –

+0

@CraigRinger .. спасибо за указание. Я только что сделал исправление. – Anant

ответ

3

Вы хотите использовать агрегат count как функцию окна, например count(id) over (partition by event_date rows 3 preceeding) ... но это сильно осложняется характером ваших данных. Вы сохраняете временные метки, а не только даты, и хотите группировать по день не по количеству предыдущих событий. В довершение всего, вы хотите перекрестно перечислить результаты.

Если PostgreSQL поддерживает RANGE в функциях окна, это будет значительно проще, чем есть. Как бы то ни было, вы должны сделать это с трудом.

Вы можете фильтровать, что через окно, чтобы получить за событие за день отставал отсчеты ... кроме того, что ваши дни события не являются смежными и, к сожалению, функция 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 для использования из внешнего запроса. Это позволит 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'); 

Я изменил идентификатор последней записи от 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

+0

большое спасибо за ответ. Позвольте мне проверить это с моим db. – Anant

+0

@Anant Только что обновлено. –

+0

@ Непревзойденная производительность, вероятно, будет довольно удивительно плоха при большом количестве данных. Подход, основанный на представлении *, может работать лучше, но, вероятно, только если у вас есть индекс на 'event_name' и, возможно, также' event_date' или 'date_trunc ('day', event_date)' –