Psql - генерировать серии с промежуточным итогом - PullRequest
1 голос
/ 22 октября 2019

У меня есть следующая таблица:

create table account_info(
    id int not null unique,
    creation_date date,
    deletion_date date,
    gather boolean)

Добавление в нее образцов данных:

insert into account_info(id,creation_date,deletion_date,gather)
values(1,'2019-09-10',null,true),
(2,'2019-09-12',null,true),
(3,'2019-09-14','2019-10-08',true),
(4,'2019-09-15','2019-09-18',true),
(5,'2019-09-22',null,false),
(6,'2019-09-27','2019-09-29',true),
(7,'2019-10-04','2019-10-17',false),
(8,null,'2019-10-20',true),
(9,'2019-10-12',null,true),
(10,'2019-10-18',null,true)

Я хотел бы посмотреть, сколько учетных записей было добавлено сгруппировано по неделям и сколькоаккаунты были удалены сгруппированы по неделям.

Я пробовал следующее:

select dd, count(distinct ai.id) as created ,count(distinct ai2.id) as deleted

from generate_series('2019-09-01'::timestamp, 
                     '2019-10-21'::timestamp, '1 week'::interval) dd
left join account_info ai on ai.creation_date::DATE <= dd::DATE
left join account_info ai2 on ai2.deletion_date::DATE <=dd::DATE
where ai.gather is true
and ai2.gather is true
group by dd
order by dd asc

Это дает следующий вывод:

 dd          | Created | Deleted |
+------------+---------+---------+
| 2019-09-22 |       4 |       1 |
| 2019-09-29 |       5 |       2 |
| 2019-10-06 |       5 |       2 |
| 2019-10-13 |       6 |       3 |
| 2019-10-20 |       7 |       4 |

Этот вывод показывает мне промежуточный итог того, сколько было созданои сколько было удалено.

Я хотел бы видеть что-то вроде этого:

+------------+---------+---------+-------------------+-------------------+
|     dd     | Created | Deleted | Total Sum Created | Total Sum Deleted |
+------------+---------+---------+-------------------+-------------------+
| 2019-09-22 | 4       | 1       |                 4 |                 1 |
| 2019-09-29 | 1       | 1       |                 5 |                 2 |
| 2019-10-06 | NULL    | NULL    |                 5 |                 2 |
| 2019-10-13 | 1       | 1       |                 6 |                 3 |
| 2019-10-20 | 1       | 1       |                 7 |                 4 |

Я получаю сообщение об ошибке при попытке суммировать столбцы created и deleted в psql. Как я не могу вложить агрегатные функции.

Ответы [ 3 ]

2 голосов
/ 22 октября 2019

Вы можете просто превратить существующий запрос в подзапрос и использовать lag() для вычисления разницы между последовательными записями:

select 
    dd,
    created - coalesce(lag(created) over(order by dd), 0) created,
    deleted - coalesce(lag(deleted) over(order by dd), 0) deleted,
    created total_sum_created,
    deleted total_sum_deleted
from (
    select 
        dd, 
        count(distinct ai.id) as created ,
        count(distinct ai2.id) as deleted
    from 
        generate_series(
            '2019-09-01'::timestamp, 
            '2019-10-21'::timestamp, 
            '1 week'::interval
        ) dd
        left join account_info ai 
            on ai.creation_date::DATE <= dd::DATE and ai.gather is true
        left join account_info ai2 
            on ai2.deletion_date::DATE <=dd::DATE and ai2.gather is true
    group by dd
) x
order by dd asc

Я переместил условия ai[2].gather = true в сторону on join: включение этих условий в предложение where в основном превращает вас left join с в inner join с.

Демонстрация DB Fiddle :

| dd                       | created | deleted | total_sum_created | total_sum_deleted |
| ------------------------ | ------- | ------- | ----------------- | ----------------- |
| 2019-09-01T00:00:00.000Z | 0       | 0       | 0                 | 0                 |
| 2019-09-08T00:00:00.000Z | 0       | 0       | 0                 | 0                 |
| 2019-09-15T00:00:00.000Z | 4       | 0       | 4                 | 0                 |
| 2019-09-22T00:00:00.000Z | 0       | 1       | 4                 | 1                 |
| 2019-09-29T00:00:00.000Z | 1       | 1       | 5                 | 2                 |
| 2019-10-06T00:00:00.000Z | 0       | 0       | 5                 | 2                 |
| 2019-10-13T00:00:00.000Z | 1       | 1       | 6                 | 3                 |
| 2019-10-20T00:00:00.000Z | 1       | 1       | 7                 | 4                 |

Другой вариант - использовать lag() в сочетании с generate_series() для создания списка диапазонов дат. Затем вы можете выполнить только одно объединение в исходной таблице и выполнить условное агрегирование во внешнем запросе:

select
    dd,
    count(distinct case 
        when ai.creation_date::date <= dd::date and ai.creation_date::date > lag_dd::date 
        then ai.id 
    end) created,
    count(distinct case 
        when ai.deletion_date::date <= dd::date and ai.deletion_date::date > lag_dd::date 
        then ai.id 
    end) deleted,
    count(distinct case 
        when ai.creation_date::date <= dd::date 
        then ai.id 
    end) total_sum_created,
    count(distinct case 
        when ai.deletion_date::date <= dd::date 
        then ai.id 
    end) total_sum_deleted
from 
    (
        select dd, lag(dd) over(order by dd) lag_dd
        from generate_series(
            '2019-09-01'::timestamp, 
            '2019-10-21'::timestamp, 
            '1 week'::interval
        ) dd
    ) dd
    left join account_info ai on ai.gather is true
group by dd
order by dd

Демонстрация на БД Fiddle

1 голос
/ 22 октября 2019

Боковое соединение и агрегация слишком хорошо подходят для этой проблемы. Если вы довольны неделями, указанными в данных:

select date_trunc('week', dte) as week,
       sum(is_create) as creates_in_week,
       sum(is_delete) as deletes_in_week,
       sum(sum(is_create)) over (order by min(v.dte)) as running_creates,
       sum(sum(is_delete)) over (order by min(v.dte)) as running_deletes
from account_info ai cross join lateral
     (values (ai.creation_date, 1, 0), (ai.deletion_date, 0, 1)
     ) v(dte, is_create, is_delete)
where v.dte is not null and ai.gather
group by week
order by week;

Если вы хотите использовать его для указанного набора недель:

select gs.wk,
       sum(v.is_create) as creates_in_week,
       sum(v.is_delete) as deletes_in_week,
       sum(sum(v.is_create)) over (order by min(v.dte)) as running_creates,
       sum(sum(v.is_delete)) over (order by min(v.dte)) as running_deletes
from generate_series('2019-09-01'::timestamp, 
                     '2019-10-21'::timestamp, '1 week'::interval) gs(wk) left join
    ( account_info ai cross join lateral
      (values (ai.creation_date, 1, 0), (ai.deletion_date, 0, 1)
      ) v(dte, is_create, is_delete)
    )
    on v.dte >= gs.wk and
       v.dte < gs.wk + interval '1 week'
where dte is not null and ai.gather
group by gs.wk
order by gs.wk;

Здесь - это дБ. <> скрипка.

1 голос
/ 22 октября 2019

Вы можете сгенерировать требуемые результаты, используя серию CTE для построения таблиц данных:

with dd as
(select *
 from generate_series('2019-09-01'::timestamp, 
                      '2019-10-21'::timestamp, '1 week'::interval) d),
ddl as
(select d, coalesce(lag(d) over (order by d), '1970-01-01'::timestamp) as pd
 from dd),
counts as
(select d, count(distinct ai.id) as created, count(distinct ai2.id) as deleted
 from ddl
 left join account_info ai on ai.creation_date::DATE > ddl.pd::DATE AND ai.creation_date::DATE <= ddl.d::DATE AND ai.gather is true
 left join account_info ai2 on ai2.deletion_date::DATE > ddl.pd::DATE AND ai2.deletion_date::DATE <= ddl.d::DATE AND ai2.gather is true
 group by d)
select d, created, deleted,
       sum(created) over (rows unbounded preceding) as "total created",
       sum(deleted) over (rows unbounded preceding) as "total deleted"
from counts
order by d asc

Обратите внимание, что условие gather должно быть частью left join, чтобы избежать поворотате во внутренние объединения.

Вывод:

d                       created     deleted     total created   total deleted
2019-09-01 00:00:00     0           0           0               0
2019-09-08 00:00:00     0           0           0               0
2019-09-15 00:00:00     4           0           4               0
2019-09-22 00:00:00     0           1           4               1
2019-09-29 00:00:00     1           1           5               2
2019-10-06 00:00:00     0           0           5               2
2019-10-13 00:00:00     1           1           6               3
2019-10-20 00:00:00     1           1           7               4

Обратите внимание, что этот запрос дает результаты за неделю, заканчивающуюся d. Если вам нужны результаты за неделю, начинающуюся с d, lag можно изменить на lead. Вы можете увидеть это в моем демо.

Демо на dbfiddle

...