Postgres: выберите запрос с предложением group by по диапазону дат - PullRequest
2 голосов
/ 26 марта 2019

Моя таблица содержит ответы из повторяющихся вопросников, которые можно заполнять в диапазоне от 30 дней и планировать каждые 60 дней.Таким образом, ответы из одного экземпляра вопросника распространяются в диапазоне дат, который всегда меньше 30 дней, и первый ответ на следующий повторяющийся вопросник составляет не менее 31 дня после последнего ответа предыдущего.Как создать представление, которое рассчитывает балл (который представляет собой сумму ответов на одну анкету) среди значений, даты которых находятся в пределах 30 дней от даты начала (минимальной даты)?

Table raw_data
------------------------------------------------
user_name | question_id | answer | answer_date |
------------------------------------------------
user001   |      1      |   2    | 2019-02-04  |
user001   |      2      |   1    | 2019-02-04  |
user001   |      3      |   2    | 2019-02-05  |
user001   |      4      |   2    | 2019-02-05  |
user001   |      5      |   2    | 2019-02-09  |
user002   |      1      |   2    | 2019-01-09  |
user002   |      2      |   2    | 2019-01-10  |
user002   |      3      |   1    | 2019-02-01  |
user002   |      4      |   2    | 2019-02-01  |
user002   |      5      |   1    | 2019-02-01  |
user002   |      1      |   2    | 2019-03-11  |
user002   |      2      |   2    | 2019-03-11  |
user002   |      3      |   1    | 2019-03-12  |
user002   |      4      |   1    | 2019-03-13  |
user002   |      5      |   1    | 2019-03-14  |


Expected result
------------------------------
user_name | sum | start_date |
------------------------------
user001   |  9  | 2019-02-04 | 
user002   |  8  | 2019-01-09 |
user002   |  7  | 2019-03-11 |

Решение, которое я попробовал, работает только для первой группы:

SELECT user_name, SUM(answer::int),
CASE 
WHEN answer_date - MIN(answer_date) OVER (PARTITION BY user_name ORDER BY user_name ASC, answer_date ASC) < 30 
THEN MIN(answer_date) OVER (PARTITION BY user_name ORDER BY user_name ASC, answer_date ASC) 
ELSE answer_date END AS start_date,
FROM public.raw_data
GROUP BY user_name, answer_date

Ответы [ 4 ]

0 голосов
/ 26 марта 2019

Вы можете использовать запрос с аналитической функцией окна row_number(), как показано ниже

with raw_data( user_name, question_id, answer, answer_date ) as
(
 select  'user001',1,2, '2019-02-04' union all
 select  'user001',2,1, '2019-02-04' union all
 select  'user001',3,2, '2019-02-05' union all
 select  'user001',4,2, '2019-02-05' union all
 select  'user001',5,2, '2019-02-09' union all
 select  'user002',1,2, '2019-01-09' union all
 select  'user002',2,2, '2019-01-10' union all
 select  'user002',3,1, '2019-02-01' union all
 select  'user002',4,2, '2019-02-01' union all
 select  'user002',5,1, '2019-02-01' union all
 select  'user002',1,2, '2019-03-11' union all
 select  'user002',2,2, '2019-03-11' union all
 select  'user002',3,1, '2019-03-12' union all
 select  'user002',4,1, '2019-03-13' union all
 select  'user002',5,1, '2019-03-14'
)    
select user_name, sum(answer) as sum, min(answer_date) as start_date
  from 
  (
   select row_number() over (partition by question_id order by user_name, answer_date) as rn,
          t.*
     from raw_data t
   ) t
  group by user_name, rn
  order by rn;

user_name   sum   start_date
---------   ---   ----------
user001     9     2019-02-04
user002     8     2019-01-09
user002     7     2019-03-11

Демо

0 голосов
/ 26 марта 2019

Благодаря @ Гордону и этому ответ В конце концов я нашел пропущенный шаг для определения моих групп на основе диапазона дат.

Я буду использовать следующий запрос для создания представления и группирования ответов SUM по grp2

WITH query AS (
SELECT r.*,
SUM(CASE WHEN answer_date < prev_date + 30 THEN 0 ELSE 1 END) OVER (PARTITION BY user_name ORDER BY user_name ASC, answer_date ASC) AS grp
  FROM (SELECT r.*,
    LAG(answer_date) OVER (PARTITION BY user_name ORDER BY user_name ASC, answer_date ASC) AS prev_date
    FROM raw_data r 
  ) r
)
SELECT user_name, question_id, answer_date, answer, DENSE_RANK() OVER (ORDER BY user_name, grp) AS grp2
FROM query
0 голосов
/ 26 марта 2019

Это классическая проблема . Вы найдете много под тегом, который я добавил.

Оптимизированный запрос для вашего случая может выглядеть так:

SELECT user_name
     , sum(answer)
     , min(answer_date) AS start_date 
FROM  (
   SELECT user_name, answer, answer_date
        , count(*) FILTER (WHERE step) OVER (PARTITION BY user_name ORDER BY answer_date) AS grp
   FROM  (
      SELECT user_name, answer, answer_date
           , lag(answer_date) OVER (PARTITION BY user_name ORDER BY answer_date) < answer_date - 30 AS step
      FROM   raw_data
      ) sub1
   ) sub2
GROUP  BY user_name, grp
ORDER  BY user_name, start_date;  -- ORDER BY optional

дБ <> скрипка здесь

Тесно связан, с дополнительным объяснением:

0 голосов
/ 26 марта 2019

Используйте lag(), чтобы найти пробелы. Затем кумулятивная сумма, чтобы назначить «период вопроса», а затем суммировать:

select userid, min(answer_date) as start_date, sum(answer)
from (select rd.*,
             count(*) filter (where prev_ad is null or prev_ad < answer_date - interval '30 day') over (partition by user_id) as period
      from (select rd.*,
                   lag(answer_date) over (partition by user_id order by answer_date) as prev_ad
            from raw_data rd
           ) rd
     )
group by userid, period;
...