我有一张表,如下所示:

CREATE TABLE time_records (
  id uuid NOT NULL,
  employee_id uuid NOT NULL,
  starttime timestampt NOT NULL,
  endtime timestampt NOT NULL
)

同一Employee_id的记录之间会有时间重叠:

id employee_id starttime endtime
1 1 '2023-09-01 07:00:00' '2023-09-01 09:15:00'
2 1 '2023-09-01 07:00:00' '2023-09-01 15:00:00'
3 1 '2023-09-01 07:00:00' '2023-09-01 15:00:00'
4 1 '2023-09-01 14:00:00' '2023-09-01 15:00:00'
5 1 '2023-09-01 23:45:00' '2023-09-01 23:59:00'
6 1 '2023-09-01 23:45:00' '2023-09-01 23:59:00'

我要做的是获取所有这些时间的时间范围:

employee_id starttime endtime ids
1 '2023-09-01 07:00:00' '2023-09-01 15:00:00' [1,2,3,4]
1 '2023-09-01 23:45:00' '2023-09-01 23:29:00' [5,6]

如果一天内只有一组重叠时间,我可以使用max/min作为开始时间和结束时间,但当一天有多组重叠时间时,我似乎无法使其工作:

select timea.employee_id,
       min(timea.starttime) starttime,
       max(timea.endtime)   endtime,
       array_agg(timea.id) ids
from time_records timea
         inner join time_records timea2 on timea.employee_id = timea2.employee_id and
                                           tsrange(timea2.starttime, timea2.endtime, '[]') &&
                                           tsrange(timea.starttime, timea.endtime, '[]')
    and timea.id != timea2.id
group by timea.employee_id;

结果:

employee_id starttime endtime ids
1 '2023-09-01 07:00:00' '2023-09-01 23:59:00' [1,2,3,4,5,6]

推荐答案

当一天中有多组重叠时间时,让它工作

使用min()max()的普通聚合不能解决这个问题.哪些行最终形成一组仅在after合并范围变得明显.

聚集功能range_agg()使任务变得简单得多.它与Postgres 14相加.现在,只需计算合并范围就非常简单:

SELECT unnest(range_agg(tsrange(starttime, endtime, '[]'))) AS merged_range
FROM   time_records;

要获得一组涉及的ID,我们需要做更多的工作.一种方法是连接回基础表,然后再次聚合(组现在由合并的范围标识):

SELECT employee_id, lower(merged) AS starttime, upper(merged) AS endtime
     , array_agg(t.id) AS ids
FROM  (
   SELECT employee_id, unnest(range_agg(tsrange(starttime, endtime, '[]'))) AS merged
   FROM   time_records
   GROUP  BY employee_id
   ) r
JOIN   time_records t USING (employee_id)
WHERE  r.merged @> t.starttime
GROUP  BY r.employee_id, r.merged
ORDER  BY r.employee_id, r.merged;

使用LATERAL个子查询的另一种方式:

SELECT r.employee_id, lower(r.merged) AS starttime, upper(r.merged) AS endtime, i.ids
FROM  (
   SELECT employee_id, unnest(range_agg(tsrange(starttime, endtime, '[]'))) AS merged
   FROM   time_records
   GROUP  BY employee_id
   ) r
CROSS  JOIN LATERAL (
   SELECT ARRAY (
      SELECT t.id
      FROM   time_records t
      WHERE  t.employee_id = r.employee_id
      AND    t.starttime <@ r.merged
      ORDER  BY t.id
      )
   ) i (ids)
ORDER  BY r.employee_id, r.merged;

fiddle个个

相关:

我不确定这两个查询是否也比下面的定制函数快,因为它只对整个表迭代一次.

Postgres 9.6

在使用过时的版本时,创建一个定制的集返回函数(一次):

CREATE OR REPLACE FUNCTION public.f_merge_ranges()
  RETURNS TABLE (
    employee_id int
  , starttime timestamp
  , endtime timestamp
  , ids int[]
  )
  LANGUAGE plpgsql AS
$func$
DECLARE
   r record;  -- current row
BEGIN
   FOR r IN 
      SELECT t.id, t.employee_id, t.starttime, t.endtime
      FROM   time_records t
      ORDER  BY t.employee_id, t.starttime, t.endtime DESC, t.id  -- better take longer range first
   LOOP
      IF r.employee_id = employee_id THEN  -- works for null in first iteration
         IF r.starttime > endtime THEN
            RETURN NEXT;
            starttime   := r.starttime;
            endtime     := r.endtime;
            ids         := ARRAY[r.id];
         ELSE
            ids         := ids || r.id;
            IF r.endtime > endtime THEN
               endtime  := r.endtime;
            END IF;
         END IF;
      ELSE
         IF employee_id IS NOT NULL THEN  -- catch first iteration
            RETURN NEXT;
         END IF;
         employee_id := r.employee_id;
         starttime   := r.starttime;
         endtime     := r.endtime;
         ids         := ARRAY[r.id];
      END IF;
   END LOOP;

   -- return last row (if any)
   IF FOUND THEN
      RETURN NEXT;
   END IF;
END
$func$;

电话:

SELECT * FROM public.f_merge_ranges();

fiddle个个

与上面的查询不同,ids中的数组是未排序的.如果你需要的话,你需要做得更多.

相关:

Sql相关问答推荐

按CTE创建任务表

为什么两个不同的窗口函数给出不同的排序结果?

如何从JSON数组中 Select 多个值

Ffltter&;Dart SQL Lite包:是否可以在一个查询中执行多条更新语句(每次执行不同的WHERE参数)

在SQL中返回缺省值,即使查询不返回任何结果

属于(日期)范围类型及其交集的总权重​

我如何才能在付款人单列中拉出只有9个付款人单的人?

在SQL中将项分配给容器

如何将insert语句重复n次使一个值递增?

如何根据几个条件 Select 值:如果满足一个范围的SUM,则对另一个范围求和

仅当 SQL Server 中的表为开时,才在存储过程中使用更改跟踪

如何为 ActiveRecord 联接应用附加条件

将一名成员金额分配给群组内的其他成员

如何根据 SQL Server 中 1 条语句中 SELECT 的结果进行 INSERT 或 UPDATE

如何使用 join 和 where 子句从另一表中仅删除一个表中的值

SQL中如何转置表格 UNPIVOT是唯一的 Select 吗?

SQL Server 查找存在于所有不同时期(或序列)中的条目

在 postgresql 中,我可以将其组合成一个查询吗?

As400 (IBM i) SQL 表 QSYS2.SYSTABLES 上的元数据

条件前置值