从存储的活动开始和结束时间中获得空闲时间
Posted
技术标签:
【中文标题】从存储的活动开始和结束时间中获得空闲时间【英文标题】:Get spare time out of stored activities start and end times 【发布时间】:2016-06-12 20:41:27 【问题描述】:我正在尝试实现一个计算存储活动开始和结束时间的空闲时间的函数。我在 PostgreSQL 9.5.3 上实现了我的数据库。这是活动表的样子
activity_id | user_id | activity_title | starts_at | ends_at
(serial) | (integer) | (text) | (timestamp without time zone) |(timestamp without time zone)
---------------------------------------------------------------------------------------------------------------------------
1 | 1 | Go to school | 2016-06-12 08:00:00 | 2016-06-12 14:00:00
2 | 1 | Visit my uncle | 2016-06-12 16:00:00 | 2016-06-12 17:30:00
3 | 1 | Go shopping | 2016-06-12 18:00:00 | 2016-06-12 21:15:00
4 | 1 | Go to Library | 2016-06-13 10:00:00 | 2016-06-13 12:00:00
5 | 1 | Install some programs on my laptop | 2016-06-13 18:00:00 | 2016-06-13 19:00:00
我的真实表的实际表定义:
CREATE TABLE public.activity (
activity_id serial,
user_id integer NOT NULL,
activity_title text,
starts_at timestamp without time zone NOT NULL,
start_tz text NOT NULL,
ends_at timestamp without time zone NOT NULL,
end_tz text NOT NULL,
recurrence text NOT NULL DEFAULT 'none'::text,
lat numeric NOT NULL,
lon numeric NOT NULL,
CONSTRAINT pk_activity PRIMARY KEY (activity_id),
CONSTRAINT fk_user_id FOREIGN KEY (user_id)
REFERENCES public.users (user_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
我想使用以(user_id INTEGER, range_start TIMESTAMP, range_end TIMESTAMP)
作为参数的 PL/pgSQL 函数计算该用户每天的空闲时间。我想要这条 SQL 语句的输出:
SELECT * from calculate_spare_time(1, '2016-06-12', '2016-06-13');
变成这样:
spare_time_id | user_id | starts_at | ends_at
(serial) | (integer) | (timestamp without time zone) |(timestamp without time zone)
----------------------------------------------------------------------------------------
1 | 1 | 2016-06-12 00:00:00 | 2016-06-12 08:00:00
2 | 1 | 2016-06-12 12:00:00 | 2016-06-12 16:00:00
3 | 1 | 2016-06-12 17:30:00 | 2016-06-12 18:00:00
4 | 1 | 2016-06-12 21:15:00 | 2016-06-13 00:00:00
5 | 1 | 2016-06-13 00:00:00 | 2016-06-13 10:00:00
6 | 1 | 2016-06-13 12:00:00 | 2016-06-13 18:00:00
7 | 1 | 2016-06-13 19:00:00 | 2016-06-14 00:00:00
我的想法是从同一日期发生的下一个活动的开始时间中减去一个活动的结束时间,但我坚持使用 PL/pgSQL 实现这一点,尤其是关于如何处理同一时间。
【问题讨论】:
希望这次编辑能明确我的问题。 现在好多了。数据有什么限制吗?活动的时间范围可以重叠吗?它们可以相邻吗?列可以为 NULL 吗?starts_at
和 ends_at
是 inclusive 还是 exclusive 界限? (下限包含,上限不包含,将是规范默认值。)允许的时间戳是否有 15 分钟的网格?实际完整的表定义(具有所有约束的CREATE TABLE
脚本)将阐明所有这些。该功能很大程度上取决于必须预期的数据。
我在帖子中添加了CREATE TABLE
脚本。仅当活动发生在lat
和lon
列表示的同一位置时,活动的时间范围才能重叠。 starts_at
是包含绑定,而ends_at
是独占绑定。
我不明白允许的时间戳上的 15 分钟网格是什么。我不明白这一点。
“15 分钟网格”表示以 15 分钟为间隔输入时间值,这将允许离散解。我添加了一个通用解决方案。
【参考方案1】:
为了简化事情,我建议创建一个视图 - 或者更好:MATERIALZED VIEW
在每个用户的活动中显示差距:
CREATE MATERIALIZED VIEW mv_gap AS
SELECT user_id, tsrange(a, z) AS gap
FROM (
SELECT user_id, ends_at AS a
, lead(starts_at) OVER (PARTITION BY user_id ORDER BY starts_at) AS z
FROM activity
) sub
WHERE z > a; -- weed out simple overlaps and the dangling "gap" till infinity
注意range type tsrange
。
注意:您提到了可能的重叠,这使事情变得复杂。如果单个用户的一个时间范围可以包含另一个,你需要做更多!合并时间范围以确定每个区块的最早开始和最晚结束。
记得在需要的时候刷新MV。
那么你的函数可以简单地是:
CREATE OR REPLACE FUNCTION f_freetime(_user_id int, _from timestamp, _to timestamp)
RETURNS TABLE (rn int, gap tsrange) AS
$func$
SELECT row_number() OVER (ORDER BY g.gap)::int AS rn
, g.gap * tsrange(_from, _to) AS gap
FROM mv_gap g
WHERE g.user_id = _user_id
AND g.gap && tsrange(_from, _to)
ORDER BY g.gap;
$func$ LANGUAGE sql STABLE;
呼叫:
SELECT * FROM f_freetime(1, '2016-06-12 0:0', '2016-06-13 0:0');
注意range operators *
and &&
。
另外注意我使用了一个简单的SQL函数,问题已经足够简化了。如果您需要添加更多内容,您可能需要切换回 plpgsql 并使用 RETURN QUERY
...
或者只使用没有函数包装的查询。
性能
如果每个用户有 很多 行,为了优化查询时间,添加一个 SP-GiST 索引(使用 MV 的一个原因):
CREATE INDEX activity_gap_spgist_idx on mv_gap USING spgist (gap);
除了(user_id)
上的索引。
此相关答案中的详细信息:
【讨论】:
非常感谢。这对我很有帮助。以上是关于从存储的活动开始和结束时间中获得空闲时间的主要内容,如果未能解决你的问题,请参考以下文章
如何获得 UITableViewCell 移动开始和结束的通知?