查找条件是否在一系列日期之间保持为真

Find if a condition stays true between a range of dates

我有一个 "action" table:: id, type, status, created, live_at, expired_at

+-----------+-----------+------------+-------------------------------+
| id        | type      | status    | created | live_at | expired_at |
| (uuid)    | (string)  | (string)  | (date)  | (date)  |  (date)    |
+-----------+-----------+------------+-------------------------------+

示例行是:

  10f1dc79-61b7-46a4-ad66-55e2a68b7148 | FACEBOOK_SOCIAL_SHARE | EXPIRED | 2019-06-21 11:28:31 | 2019-07-21 11:28:36 | 2019-10-02 11:40:27 
  3e59ccd4-a795-4e74-b841-4da1e57fb51f | FACEBOOK_SOCIAL_SHARE | LIVE    | 2019-10-04 18:25:57 | 2019-10-04 18:25:57 | NULL                

我必须 运行 一个查询,我可以在其中获取按类型和月份分组的所有实时操作。

示例结果:

TYPE                    MONTH LIVE
FACEBOOK_SOCIAL_SHARE     7     1
FACEBOOK_SOCIAL_SHARE     8     1
FACEBOOK_SOCIAL_SHARE     9     5
FACEBOOK_SOCIAL_SHARE     10    9

问题是,如果某个操作在第 8 个月上线并在第 10 个月的某天过期,那么查询应该将该操作也计为在第 8、9 和 10 个月内上线。

我有一个问题,但它只会将此操作计为 8 月份的活动!

SELECT TYPE, EXTRACT(MONTH FROM action.live_at) AS month, count(distinct(action.id)) AS live
FROM "action" AS action
WHERE action.live_at IS NOT NULL
GROUP BY TYPE, EXTRACT(MONTH FROM action.live_at)

任何帮助将不胜感激。

据我了解你的问题,我认为以下内容可以满足你的要求:

with actions as (
  select id, type, 
         array(select extract(month from x.dt)::int
               from generate_series(date_trunc('month', live_at), 
                                    date_trunc('month', coalesce(expired_at, current_timestamp)) + interval '1 month' - interval '1 day', 
                                    interval '1 month') as x(dt)) as months_live
  from  action
)
select m.month, type, count(distinct a.id)
from generate_series(1,12) as m(month)
  left join actions a on m.month = any(a.months_live)
group by m.month, type;

CTE 为 action table 中的每一行生成所有月份的数组。因此,对于您的两个示例行,这将 return

id                                   | months_live
-------------------------------------+------------
10f1dc79-61b7-46a4-ad66-55e2a68b7148 | {7,8,9,10} 
3e59ccd4-a795-4e74-b841-4da1e57fb51f | {10,11}    

表达式 date_trunc('month', coalesce(expired_at, current_timestamp)) + interval '1 month' - interval '1 day' 生成 expired_at 包含的月份的最后一天。这是必要的,以便 generate_series() 也包括那个月。

我现在不知道应该如何处理 expired_at 列中的 null 值 - 上面的表达式只是使用 "today" 然后。

外部查询然后在 12 个月的列表和操作之间进行外部连接 - 由于连接条件基于数组,操作 table 中的一行重复多次,因为连接条件匹配多次。

外部联接(不分组)return以下行(基于您的两个样本行,今天是 11 月的一天):

month | type                  | id                                  
------+-----------------------+-------------------------------------
    1 |                       |                                     
    2 |                       |                                     
    3 |                       |                                     
    4 |                       |                                     
    5 |                       |                                     
    6 |                       |                                     
    7 | FACEBOOK_SOCIAL_SHARE | 10f1dc79-61b7-46a4-ad66-55e2a68b7148
    8 | FACEBOOK_SOCIAL_SHARE | 10f1dc79-61b7-46a4-ad66-55e2a68b7148
    9 | FACEBOOK_SOCIAL_SHARE | 10f1dc79-61b7-46a4-ad66-55e2a68b7148
   10 | FACEBOOK_SOCIAL_SHARE | 10f1dc79-61b7-46a4-ad66-55e2a68b7148
   10 | FACEBOOK_SOCIAL_SHARE | 3e59ccd4-a795-4e74-b841-4da1e57fb51f
   11 | FACEBOOK_SOCIAL_SHARE | 3e59ccd4-a795-4e74-b841-4da1e57fb51f
   12 |                       |                                     

此结果然后按月份和类型分组,以便能够计算 ID。

因此您的两个示例行将 return:

month | type                  | count
------+-----------------------+------
    1 |                       |     0
    2 |                       |     0
    3 |                       |     0
    4 |                       |     0
    5 |                       |     0
    6 |                       |     0
    7 | FACEBOOK_SOCIAL_SHARE |     1
    8 | FACEBOOK_SOCIAL_SHARE |     1
    9 | FACEBOOK_SOCIAL_SHARE |     1
   10 | FACEBOOK_SOCIAL_SHARE |     2
   11 | FACEBOOK_SOCIAL_SHARE |     1
   12 |                       |     0

在线示例:https://rextester.com/NYUV51842

如果您经常需要它,请考虑编写一个函数:

create or replace function get_month_list(p_start timestamp, p_end timestamp)  
  returns int[]
as
$$
 select array(select extract(month from x.dt)::int
              from generate_series(date_trunc('month', p_start), 
                            date_trunc('month', coalesce(p_end, current_timestamp)) + interval '1 month' - interval '1 day', 
                            interval '1 month') as x(dt));
$$ 
language sql
immutable;

那么查询更容易阅读:

select m.month, type, count(distinct a.id)
from generate_series(1,12) as m(month)
  left join action a on m.month = any(get_month_list(a.live_at, a.expired_at))
group by m.month, type;