【问题标题】:Sum of time difference between rows行间时间差之和
【发布时间】:2014-04-02 14:31:37
【问题描述】:

我有一张记录实体每一次状态变化的表格

id      recordTime        Status
ID1 2014-03-01 11:33:00 Disconnected  
ID1 2014-03-01 12:13:00 Connected  
ID2 2014-03-01 12:21:00 Connected  
ID1 2014-03-01 12:24:00 Disconnected  
ID1 2014-03-01 12:29:00 Connected  
ID2 2014-03-01 12:40:00 Disconnected  
ID2 2014-03-01 13:03:00 Connected  
ID2 2014-03-01 13:13:00 Disconnected  
ID2 2014-03-01 13:29:00 Connected  
ID1 2014-03-01 13:30:00 Disconnected

我需要计算给定时间窗口内每个 ID 的总非活动时间,即“已连接”和上次“已断开”状态之间的时间。

对于上表和 2014-03-01 11:00:00 到 2014-03-01 14:00:00 的时间范围,输出应为:

ID  InactiveTime
ID1  01:15:00
ID2  02:00:00

【问题讨论】:

  • 你确定你想要的输出吗?
  • 是否保证任何给定id 的下一行具有相反的状态?
  • @Houari:如果考虑给定的时间范围2014-03-01 11:00:00 to 2014-03-01 14:00:00,输出是正确的。
  • @Erwin,是的,每个条目的状态肯定会切换。
  • @ErwinBrandstetter 我明白了 :)

标签: sql postgresql aggregate-functions window-functions


【解决方案1】:

特别的困难是不要错过外部时间框架的时间跨度。
假设任何给定 id 的下一行始终具有相反的状态。
使用列名ts 而不是recordTime

WITH span AS (
   SELECT '2014-03-01 13:00'::timestamp AS s_from  -- start of time range
        , '2014-03-01 14:00'::timestamp AS s_to    -- end of time range
   )
, cte AS (
   SELECT id, ts, status, s_to
        , lead(ts, 1, s_from) OVER w AS span_start
        , first_value(ts)     OVER w AS last_ts
   FROM   span s
   JOIN   tbl  t ON t.ts BETWEEN s.s_from AND s.s_to
   WINDOW w AS (PARTITION BY id ORDER BY ts DESC)
   )
SELECT id, sum(time_disconnected)::text AS total_disconnected
FROM  (
   SELECT id, ts - span_start AS time_disconnected
   FROM   cte
   WHERE  status = 'Connected'

   UNION  ALL  
   SELECT id, s_to - ts
   FROM   cte
   WHERE  status = 'Disconnected'
   AND    ts = last_ts
   ) sub
GROUP  BY 1
ORDER  BY 1;

根据要求返回间隔。
在所选时间范围内没有条目的 ID 不会显示。您必须另外查询它们。

SQL Fiddle.
注意:我将生成的total_disconnected 转换为小提琴中的text,因为interval 类型的显示格式很糟糕。

在选定的时间范围内添加没有条目的 ID

评论中的每个请求。
添加到上面的查询中(在最后的ORDER BY 1 之前):

...
UNION  ALL
SELECT id, total_disconnected
   FROM  (
   SELECT DISTINCT ON (id)
          t.id, t.status, (s.s_to - s.s_from)::text AS total_disconnected
   FROM   span     s
   JOIN   tbl      t ON t.ts < s.s_from  -- only from before time range
   LEFT   JOIN cte c USING (id)
   WHERE  c.id IS NULL         -- not represented in selected time frame
   ORDER  BY t.id, t.ts DESC   -- only the latest entry
   ) sub
WHERE  status = 'Disconnected' -- only if disconnected
ORDER  BY 1;

SQL Fiddle.

现在,只有在或之前所选时间范围内没有条目的 ID 不会显示。

【讨论】:

  • 如果我将时间范围从 11:00 更改为 14:00 到 13:00 到 14:00,为什么会显示混乱的结果?
  • @Sandeep:我的第一个版本没有为超出时间范围的行做好准备。考虑使用更新的小提琴的更新版本。
  • 真的很快。谢谢!
  • 对于您的评论“在所选时间范围内没有条目的 ID 不会显示。您必须另外查询它们。”,您的建议是什么?您如何看待我的回答,它基本上是您的增强版,它还考虑了“在选定时间内没有条目的 ID”
  • @Sandeep:考虑一下解决这个问题的附录。
【解决方案2】:

这就是我对你的问题的理解SQL Fiddle

select id, sum(diff) as inactive
from (
    select
        recordtime,
        recordTime -
            lag(recordTime, 1, recordTime)
            over(
                partition by id
                order by recordTime
            )
        as diff,
        status,
        id
    from t
) s
where status = 'Connected'
group by id
order by id
;
 id | inactive 
----+----------
  1 | 00:45:00
  2 | 00:39:00

你能解释一下你想要的输出吗?

【讨论】:

  • @Houari:也错过了同样重要的细节。 ;)
  • 正如欧文在他的回答中提到的“特别的困难是不要错过外部时间框架的时间跨度。”
【解决方案3】:
select id , sum(diff) inactif_time
from
(
SELECT id, "recordTime", "Status" ,LEAD("recordTime") OVER(PARTITION BY id order by "recordTime" ),LEAD("recordTime") OVER(PARTITION BY id order by "recordTime" ) - "recordTime" diff
  FROM my_table
) B 
where "Status" = 'Disconnected'
group by id

但它输出:

"ID1";"00:45:00"
"ID2";"00:39:00"

【讨论】:

    猜你喜欢
    • 2018-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多