我有下表:
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
如何实现这一点?
这是一个有趣的问题。 我稍微简化了列命名:
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 ;
这是一个数据库<>小提琴。
我意识到持续时间列是不必要的。 您正在尝试以 15 分钟的间隔捕获重叠秒数。
这会在小时边界上创建间隔 - 这更容易。 这意味着某些重叠不正确,这是where
子句过滤掉的内容。
立即转换为秒并使用整数计算应该是最快的:
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<>fiddle here(将角落用例添加到测试用例中)
要点
extract(epoch FROM datetimeconnect)
摘录(引用手册)...自 1970-01-01 00:00:00 UTC 以来的秒数
generate_series()
(整数变体)准确生成所需的行数,没有多余的。准确地说,每个相关四分之一小时的下限 - 您在结果中显示的值。a / 900 * 900
利用整数除法向下舍入到整刻钟(900 的倍数)。使用它,因为date_trunc()
没有选择截断到四分之一小时least(q + 900, b) - greatest(a, q)
确保正确计算开始和结束,即使两者都位于同一刻钟内(如小提琴中的扩展测试用例所示)。to_timestamp(q) AT TIME ZONE 'UTC'
因为to_timestamp()
返回timestamptz
,我们希望从中得到UTC的相应timestamp
。
旁白:duration
是一个功能相关的值。它不是任务所必需的,不应冗余存储在表中。这只是压舱物减慢了一切。它可以廉价地即时计算。
在 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;
DBFiddle 上的这个演示返回:
| 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 |