postgresql - 从存储的事件开始和结束时间中获取空闲时间

标签 postgresql function range plpgsql gaps-and-islands

我正在尝试实现一个函数来计算存储的事件开始和结束时间的空闲时间。我在 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 实现它,尤其是关于如何处理同时。

最佳答案

为了简化事情,我建议创建一个 View - 或者更好:一个 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 .

注意:您提到了可能的重叠,这使事情变得复杂。如果单个用户的一个时间范围可以包含在另一个时间范围内,您需要做更多的事情!合并时间范围以识别每个 block 的最早开始和最晚结束。

记得在需要的时候刷新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) 上的索引。
此相关答案中的详细信息:

关于postgresql - 从存储的事件开始和结束时间中获取空闲时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37778881/

相关文章:

swift - 既然 C-Style for 循环将从 Swift 3 中移除,一种不执行循环的简洁方法?

function - Scala 是否有类似于 Haskell 的 `$` 的运算符?

sql - MySQL:哪些索引用于简单范围选择?

java - PSQL异常 : Syntax Error near at or "Entity" - Spring Boot

sql - postgresql函数-获取受更新查询影响的行数

mysql - 如何设置 Ruby on Rails 4+ 应用程序的默认数据库隔离级别

ruby-on-rails - postgresql 和 rails 4 的二进制文件附件 "unrecognizable format"

php - 如何在 PHP 生成的脚本中调用 javascript 函数?

c++ - 通过函数遍历c数组

haskell - 为什么在 Haskell 中使用范围时 map 会返回一个附加元素?