根据观看的节目类别将一条记录拆分为多条记录

split a record into multiple records based on watched program category

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

问题详情:

我有两个 table:

  1. usage_detail
  2. Program_info

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 table 包含预定的节目详细信息和相应的类别。

例如:

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 pm 到 1:30 pm,1:30 pm 到 2:00pm,2:00 pm 到 2:25 pm) 然后根据 program_category.

总结持续时间

我写了一些可读性较差的代码,它在不使用 postgres 的数组和 unnest 功能的情况下动态地从一条记录中生成多条记录。

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

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

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() 相同的语言存储工作日 return。最好将其存储为数字,而不是字符串。

有了这个结果,就可以计算出每个节目的实际开始和结束时间。这可以通过复杂的 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。上面的return是这样的:

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

这个returns:

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