在间隔时段中分配时间

时间:2019-03-21 23:28:03

标签: sql postgresql date-arithmetic generate-series

我有下表:

CREATE TABLE f_contact (
    agent character varying,
    datetimeconnect timestamp without time zone,
    datetimedisconnect timestamp without time zone,
    duration integer
);

duration是断开时间和连接时间之间的时间(以秒为单位)。
我可能有这样的数据:

agent   datetimeconnect         datetimedisconnect      duration
20024   2019-03-18 12:01:00.0   2019-03-18 13:01:30.0   3630
20011   2019-03-11 08:47:40.0   2019-03-11 09:30:10.0   2550

我想获取数据并以15分钟为间隔分配时间,所以我得到这样的结果:

20024   12:00   840
20024   12:15   900
20024   12:30   900
20024   12:45   900 
20024   13:00   90
20011   08:45   740
20011   09:00   900
20011   09:15   900
20011   09:30   10

如何实现?

3 个答案:

答案 0 :(得分:2)

这是一个有趣的问题。我稍微简化了列命名:

with t as (
      select 20024 as agent, '2019-03-18 12:01:00.0'::timestamp as conn, '2019-03-18 13:01:30.0'::timestamp as disconn, 3630 duration union all
      select 20011, '2019-03-11 08:47:40.0', '2019-03-11 09:30:10.0', 2550
     )
select gs.t, t.*,
       extract(epoch from least(gs.t + interval '15 minute', disconn) - greatest(gs.t, conn))
from t cross join lateral
     generate_series(date_trunc('hour', t.conn), date_trunc('hour', t.disconn) + interval '1 hour', interval '15 minute') gs(t)
where conn <= gs.t + interval '15 minute' and disconn >= gs.t ;

Here是db <>小提琴。

我意识到,持续时间栏是不必要的。您正在尝试以15分钟的间隔捕获重叠的秒数。

这会在小时边界上创建间隔-这更容易。这意味着某些重叠是不正确的,这就是where子句过滤掉的内容。

答案 1 :(得分:1)

立即转换为秒并使用整数进行计算应该最快:

SELECT agent
     , to_char(to_timestamp(q) AT TIME ZONE 'UTC', 'HH24:MI') AS quarter_hour
     , least(q + 900, b) - greatest(a, q) AS seconds
FROM   (
   SELECT agent
        , extract(epoch FROM datetimeconnect)::int    AS a
        , extract(epoch FROM datetimedisconnect)::int AS b
   FROM   f_contact
   )  f, generate_series(a / 900 * 900, b, 900) q
ORDER  BY agent DESC, q;

产生所需的结果。

db <>小提琴here (在测试用例中添加了边角案例)

要点

  • extract(epoch FROM datetimeconnect)提取(引用the manual)...

      

    自1970-01-01 00:00:00 UTC以来的秒数

  • generate_series()(整数变体)精确生成所需的行数,没有多余的行。确切地说,每个相关的四分之一小时的下限-您在结果中显示的值。

  • a / 900 * 900使用integer division向下舍入整整四分之一小时(900的倍数)。自date_trunc()起就使用此选项,无法将其截断为四分之一小时

  • least(q + 900, b) - greatest(a, q)确保即使开始时间和结束时间都在同一刻钟之内(如小提琴中扩展的测试案例所示),也可以确保正确地计算了开始和结束的时间。

  • to_timestamp(q) AT TIME ZONE 'UTC',因为to_timestamp()返回timestamptz,因此我们希望从UTC返回timestamp

此外:duration是功能相关的值。它不需要执行任务,因此不应冗余地存储在表中。那只是镇流器,使一切都变慢了。它可以廉价地即时计算。

答案 2 :(得分:0)

在Postgres中,您可以使用generate_series()来生成数据系列。我将从生成一系列数字开始,然后用原始数据JOIN将其生成,以生成15分钟的时段。内部查询可用于预先计算开始和结束范围。

考虑以下查询,该查询演示了将时间戳四舍五入到15分钟并用该系列JOIN表的逻辑:

    SELECT *
    FROM generate_series(0, 99, 1) t(x)
    INNER JOIN (
        SELECT 
            f.*,
            DATE_TRUNC('hour', datetimeconnect)    
                + DATE_PART('minute', datetimeconnect   )::int / 15 * interval '15 min' connect_15min,
            DATE_TRUNC('hour', datetimedisconnect) 
                + DATE_PART('minute', datetimedisconnect)::int / 15 * interval '15 min' disconnect_15min
        FROM f_contact f
    ) c 
        ON c.disconnect_15min >= c.connect_15min + ((t.x * 15) || ' minute')::interval 
    ORDER BY c.datetimeconnect, t.x;

例如,对于agent = 2011,它将返回:

| x   | agent | datetimeconnect          | datetimedisconnect       | duration | connect_15min            | disconnect_15min         |
| --- | ----- | ------------------------ | ------------------------ | -------- | ------------------------ | ------------------------ |
| 0   | 20011 | 2019-03-11T08:47:40.000Z | 2019-03-11T09:30:10.000Z | 2550     | 2019-03-11T08:45:00.000Z | 2019-03-11T09:30:00.000Z |
| 1   | 20011 | 2019-03-11T08:47:40.000Z | 2019-03-11T09:30:10.000Z | 2550     | 2019-03-11T08:45:00.000Z | 2019-03-11T09:30:00.000Z |
| 2   | 20011 | 2019-03-11T08:47:40.000Z | 2019-03-11T09:30:10.000Z | 2550     | 2019-03-11T08:45:00.000Z | 2019-03-11T09:30:00.000Z |
| 3   | 20011 | 2019-03-11T08:47:40.000Z | 2019-03-11T09:30:10.000Z | 2550     | 2019-03-11T08:45:00.000Z | 2019-03-11T09:30:00.000Z |

现在,我们可以在FROM子句中计算持续时间。诀窍是使用LEAST()GREATEST()正确处理第一个和最后一个间隔(请注意,duration并不用于计算):

SELECT 
    agent,
    c.connect_15min + ( t.x * 15 || ' minute' )::interval interval_start_15min,

    EXTRACT(EPOCH FROM (
        LEAST(datetimedisconnect, c.connect_15min + ( (t.x + 1) * 15 || ' minute' )::interval) 
        - GREATEST(datetimeconnect, c.connect_15min + ( t.x * 15 || ' minute' )::interval )
    )) duration
FROM generate_series(0, 99, 1) t(x)
INNER JOIN (
    SELECT 
        f.*,
        DATE_TRUNC('hour', datetimeconnect)    
            + DATE_PART('minute', datetimeconnect   )::int / 15 * interval '15 min' connect_15min,
        DATE_TRUNC('hour', datetimedisconnect) 
            + DATE_PART('minute', datetimedisconnect)::int / 15 * interval '15 min' disconnect_15min
    FROM f_contact f
) c 
    ON c.disconnect_15min >= c.connect_15min + ((t.x * 15) || ' minute')::interval 
ORDER BY agent, interval_start_15min;

demo on DB Fiddle 返回:

| agent | interval_start_15min     | duration |
| ----- | ------------------------ | -------- |
| 20011 | 2019-03-11T08:45:00.000Z | 740      |
| 20011 | 2019-03-11T09:00:00.000Z | 900      |
| 20011 | 2019-03-11T09:15:00.000Z | 900      |
| 20011 | 2019-03-11T09:30:00.000Z | 10       |
| 20024 | 2019-03-18T12:00:00.000Z | 840      |
| 20024 | 2019-03-18T12:15:00.000Z | 900      |
| 20024 | 2019-03-18T12:30:00.000Z | 900      |
| 20024 | 2019-03-18T12:45:00.000Z | 900      |
| 20024 | 2019-03-18T13:00:00.000Z | 90       |