【问题标题】:Split table data based on time gaps根据时间间隔拆分表数据
【发布时间】:2022-01-10 13:33:28
【问题描述】:

假设我们有一个实体元数据的时间序列数据集导入到 postgres 表Stats

CREATE EXTENSION IF NOT EXISTS POSTGIS;
DROP TABLE IF EXISTS "Stats";
CREATE TABLE IF NOT EXISTS "Stats"
(
    "time" BIGINT,
    "id" BIGINT,
    "position" GEOGRAPHY(PointZ, 4326)
);

这里是表格示例:

SELECT 
    "id",
    "time"
FROM
    "Stats"
ORDER BY 
    "id", "time" ASC

id|time|
--+----+
 1|   3|
 1|   4|
 1|   6|
 1|   7|
 2|   2|
 2|   6|
 3|  14|
 4|   2|
 4|   9|
 4|  10|
 4|  11|
 5|  32|
 6|  15|
 7|  16|

业务需求是为该表中的实体分配 route-id,所以当每个实体的时间跳过1 second 时,表示该实体的新航班或路线。以前的样本的最终结果是这样的:

id|time|route_id|
--+----+--------+
 1|   3|       1|
 1|   4|       1|
 1|   6|       2|
 1|   7|       2|
 2|   2|       1|
 2|   6|       2|
 3|  14|       1|
 4|   2|       1|
 4|   9|       2|
 4|  10|       2|
 4|  11|       2|
 5|  32|       1|
 6|  15|       1|
 7|  16|       1|

这将是新的路线汇总表:

id|start_time|end_time|route_id|
--+----------+--------+--------+
 1|         3|       4|       1|
 1|         6|       7|       2|
 2|         2|       2|       1|
 2|         6|       6|       2|
 3|        14|      14|       1|
 4|         2|       2|       1|
 4|         9|      11|       2|
 5|        32|      32|       1|
 6|        15|      15|       1|
 7|        16|      16|       1|

那么这个复杂的查询应该如何构造呢?

【问题讨论】:

  • 如果我理解正确,对于 ID 4 on time 10,routeID 必须是 2。为什么提供了 3?
  • @Arun。是的,没错,它只有两条 ID 为 1、2 的路由
  • 差距和岛屿。在这里解决了之前的时间。
  • @shawnt00。实际上这是一个从opensky-network.org/datasets/states 导入的历史飞机航班数据集,应该用路线建模!
  • 数据的细节不会改变查询的性质。查找间隙和岛屿,您会发现同一主题的数百种变化。

标签: sql postgresql postgis window-functions


【解决方案1】:

假设您手头有表stats,以下查询将通过分配route_id 创建一个表:

使用recursive-cte查询分配route_id:

CREATE TABLE tbl_route AS 
with recursive cte AS 
(
  SELECT id,  prev_time, time, rn, rn AS ref_rn, rn AS route_id 
  FROM 
  (    
    SELECT 
      *,
      lag(time) OVER(partition BY id ORDER BY time) AS prev_time,
      row_number() OVER(partition BY id ORDER BY time) AS rn 
    FROM stats
  ) AS rnt
  WHERE rn=1

  UNION

  SELECT rnt2.id, rnt2.prev_time, rnt2.time, rnt2.rn, cte.rn AS ref_rn,
    CASE 
      WHEN abs(rnt2.time-rnt2.prev_time)<=1 THEN cte.route_id
      ELSE cte.route_id+1
    END AS route_id
  FROM cte
  INNER JOIN
  (
   SELECT 
     *,
     lag(time) OVER(partition BY id ORDER BY time) AS prev_time,
     row_number() OVER(partition BY id ORDER BY time) AS rn 
   FROM stats
  ) AS rnt2
   ON cte.id=rnt2.id AND cte.rn+1 = rnt2.rn
)

SELECT id, time, route_id FROM cte;

查询分配的 route_id 是否正确:

select id, time, route_id 
from tbl_route 
order by id, time

查询创建new summary表:

select id, min(time) as start_time, max(time) as end_time, route_id
from tbl_route
group by id, route_id
order by id, route_id, start_time, end_time

递归-CTE 查询细分:

由于使用了递归 cte,查询可能看起来很混乱。但是,我尝试将其分解如下:

  1. 使用 UNION 附加了 2 个主要查询,第一个将分配 route_id 用于每个 id 的开头,第二个将分配给每个 id 的其余行
  2. rntrnt2 已创建,因为我们需要 ROW_NUMBERLAG 值来实现此目标
  3. 我们加入 cte 和 rnt2 recursively 通过检查时间差来分配 route_id

DEMO

【讨论】:

    【解决方案2】:
    with data as (
        select *, row_number() over (partition by id order by "time") rn from Stats
    )
    select id,
        min("time") as start_time, max("time") as end_time,
        row_number() over (partition by id order by "time" - rn) as route_id
    from data
    group by id, "time" - rn
    order by id, "time" - rn
    

    https://dbfiddle.uk/?rdbms=postgres_9.5&fiddle=c272bc57786487b0b664648139530ae4

    【讨论】:

      猜你喜欢
      • 2018-04-20
      • 1970-01-01
      • 2020-09-20
      • 2020-02-23
      • 1970-01-01
      • 1970-01-01
      • 2021-06-20
      • 1970-01-01
      • 2017-10-24
      相关资源
      最近更新 更多