根据观看的节目类别将记录分成多个记录

时间:2016-10-31 08:45:46

标签: sql arrays postgresql unnest

我最近开始使用postgres,我来自oracle背景。只是想知道我写的查询是否可以在postgres中以更好的方式实现。

问题详情:

我有两张桌子:

  1. usage_detail
  2. Program_info
  3. Usage_detail包含有关观看频道的任何用户的信息。例如,用户A的会话长度为1小时10分0秒,从今天下午1:15开始

    User  start_time           end_time
    A     2016-10-31 13:15:00  2016-10-31 14:25:00
    

    Program_info表包含预定的程序详细信息和相应的类别。

    例如:

    Program_id program_category  week_day   start_time  end_time
             1 News              Monday     13:00       13:30
             2 Sports            Monday     13:30       14:30
    

    我正在寻找的输出是:

    User  program_category   start_time           duration (in seconds)
       A  News               2016-10-31 13:15:00       900
       A  Sports             2016-10-31 13:30:00      3300
    

    我目前的做法:

    我将start_time和end_time持续时间分为30分钟(因为程序类别可能每30分钟更改一次)。就像我提到的例子一样,我首先创建了3条记录(从下午1:15到下午1:30,下午1:30到下午2:00,下午2:00到下午2:25),然后根据program_category对持续时间求和

    我编写了一个不太可读的代码,它可以动态生成一条记录中的多条记录而不使用postgres的数组和不需要的功能。

    有人建议使用Array / unnest或postgres中可用的任何其他功能来解决此问题的最佳方法是什么?我不是在寻找确切的代码,只是方向会做。

1 个答案:

答案 0 :(得分:1)

我认为你不需要生成任何行。根据您的示例数据,您可以简单地加入这两个表。

select *
from program_info pi
  join usage_detail ud 
    on to_char(ud.start_time, 'FMday') = lower(pi.week_day) 
   and (pi.start_time, pi.end_time) overlaps (ud.start_time::time, ud.end_time::time)

(我使用user_name代替user,因为user是保留关键字)

请注意,使用to_char(ud.start_time, 'FMday') = lower(pi.week_day)的联接要求工作日以与to_char()相同的语言存储,并将其返回。最好将其存储为数字,而不是字符串。

通过该结果,可以计算每个节目的实际开始和结束时间。这可以通过复杂的case when语句来完成,该语句将usage_detail中存储的时间信息与来自program_info的时间信息进行比较,以检查哪个开始时间是较大的,哪个结束时间是较小的一个。

然而,这可以使用时间范围来简化。遗憾的是,内置的时间范围没有,但它很容易创建:

create type timerange as range (subtype = time);

可以使用两个范围的交集来计算实际的开始和结束时间:

select ud.user_name, 
       pi.program_id,
       pi.program_category,
       ud.start_time::date as start_day,
       timerange(pi.start_time, pi.end_time) * timerange(ud.start_time::time, ud.end_time::time) as view_interval
from program_info pi
  join usage_detail ud 
    on to_char(ud.start_time, 'FMday') = lower(pi.week_day) 
   and (pi.start_time, pi.end_time) overlaps (ud.start_time::time, ud.end_time::time)

*是范围的intersection operator。以上回复:

user_name | program_id | program_category | start_day  | view_interval      
----------+------------+------------------+------------+--------------------
A         |          1 | News             | 2016-10-31 | [13:15:00,13:30:00)
A         |          2 | Sports           | 2016-10-31 | [13:30:00,14:25:00)

现在可以使用实际观看时间作为范围来获得所需的最终显示:

with view_times as (
    select ud.user_name, 
           pi.program_id,
           pi.program_category,
           ud.start_time::date as start_day,
           timerange(pi.start_time, pi.end_time) * timerange(ud.start_time::time, ud.end_time::time) as view_interval
    from program_info pi
      join usage_detail ud 
        on to_char(ud.start_time, 'FMday') = lower(pi.week_day) 
       and (pi.start_time, pi.end_time) overlaps (ud.start_time::time, ud.end_time::time)
)
select user_name, program_id, program_category,
       start_day + lower(view_interval) as actual_start_time,
       extract(epoch from (upper(view_interval) - lower(view_interval))) as duration
from view_times

返回:

user_name | program_id | program_category | actual_start_time   | duration
----------+------------+------------------+---------------------+---------
A         |          1 | News             | 2016-10-31 13:15:00 |      900
A         |          2 | Sports           | 2016-10-31 13:30:00 |     3300

在线示例:http://rextester.com/VNXIG64065