从存储的活动开始和结束时间中获取空闲时间

Get spare time out of stored activities start and end times

我正在尝试实现一个函数来计算存储的活动开始和结束时间的空闲时间。我在 PostgreSQL 9.5.3 上实现了我的数据库。这就是 activity table 的样子

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

实际table我的真实定义table:

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

我想从发生在同一天的下一个 activity 的开始时间减去一个 activity 的结束时间,但我坚持用 PL/pgSQL 特别是关于如何同时处理 2 行。

为了简化事情,我建议创建一个视图 - 或者更好:一个 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) 上的索引。
此相关答案中的详细信息:

  • Perform this hours of operation query in PostgreSQL