设备停机时长如何算对:重叠区间合并、班次裁剪与 SQL 验收

设备同时出现多条故障记录时,逐条累加持续时间会把同一分钟算多次;仅比较上一条结束时间,又可能被嵌套区间误导。本文从停机指标契约出发,用构造数据解释区间并集,给出运行最大终点与 PostgreSQL 多范围两种实施路径,并覆盖跨班次、未结束事件、原因归属和数据缺失的验收办法。

一台设备在一小时内报了三次故障,不代表停机了三段互不重叠的时间。停机报表和智能问数需要计算“处于停机状态的时间集合有多长”,而非“故障记录时长相加有多少”。本文假定输入已能表达可信的停机区间,使用构造示例推导;SQL 语义依据 PostgreSQL 18 文档,不宣称生产环境实测。

固定设备、统计窗口和停机定义

先把指标写成一句完整定义:对指定设备,在指定计划生产时间内,至少存在一个符合规则的停机事件的实际经过时长。事件必须代表设备停机;仅有告警、维修工单或通信中断,未必能直接证明停机。若输入只是离散状态点,应先用有最大有效期和未知状态处理的规则构造区间,本篇不把相邻点间的状态自动视为已知。

可靠的企业 Text-to-SQL 需要先确定指标含义,再生成查询。这里至少要冻结设备标识、起止字段、统计时区、时间精度、计划生产窗口、排除条件和数据截止时刻。跨设备求和是设备分钟,产线完全停顿的墙钟分钟是另一项指标,后者需要说明哪些设备组合会让产线停顿。

统一使用左闭右开区间 [start,end)。起点计入、终点不计入,使相邻班次能在同一边界接合而不重复归属。PostgreSQL 提供带时区时间戳范围 tstzrange,以及可表示多个分离区间的多范围类型。[1] 类型只能表达边界,不能替业务判断一条告警是否属于停机。

构造反例:必须与此前最大终点比较

构造同一设备的三条事件,均在同一天且时区相同:A 为 08:00—09:00,B 为 08:10—08:20,C 为 08:30—09:10。逐条时长是 60、10、40 分钟,求和为 110;它们的并集是 08:00—09:10,真实的区间覆盖时长为 70 分钟。本例中 B 完全包含在 A 内,不增加停机时间。

如果按开始时间排序,只用 LAG(end) 判断当前记录是否与上一条分离,C 会拿自己的 08:30 与 B 的 08:20 比较,被误判为新组。实际上 A 仍覆盖到 09:00。需要比较的是所有前序记录的最大结束时间,而非紧邻上一行的结束时间。LAG 在 PostgreSQL 中返回指定前序行的值,并不替你计算这个最大值。[4]

定义 prior_end 为当前行之前所有行的结束时间最大值。首行启动一组;其余行仅当 start 大于 prior_end 时启动新组。随后对组标记累计求和形成 group_id,再对每组取最小开始、最大结束。若 start 等于 prior_end,本文将相邻区间合并以简化时长计算;若业务要保留故障次数,相邻事件的身份必须另存,不能由合并段数推断次数。

先裁剪统计边界,再计算有限区间

对窗口 [W0,W1),每个有效事件裁剪为 s=max(start,W0),e=min(end,W1),仅保留 s<e 的片段。构造窗口 08:15—09:00,上述 A 裁为 45 分钟、B 裁为 5 分钟、C 裁为 30 分钟;逐条相加是 80,但并集只有 45。先看总和小于班次长度才发现错误并不可靠,有些重复计算仍会落在合法范围内。

开始为空、结束早于开始、无法解析的时区和冲突版本应进入质量异常流程。结束为空可能表示仍在停机,也可能表示漏录;不能不加区分地替换成当前时刻。仅对来源明确标记为活动中的事件,按冻结的数据截止时刻 T 暂定结束,且最终不超过统计窗口结束。重复查询同一快照时使用同一个 T,避免报表随服务器时钟漂移。

如果 T 早于 W1,后续时段不是已观测正常时间。结果应标为截至 T 的暂定覆盖,并说明计划窗口尚未完整。零长度事件不贡献时长,但可以保留用于事件数量审计。设备时钟修正、迟到关闭和人工撤销应产生版本;旧报表若被重算,保存修订原因及受影响窗口。

班次内有多个计划生产片段时,先建立不重叠的计划时间集合,再与停机集合取交集。午休是否排除取决于指标契约,不应靠把开始和结束各减一个固定数来处理。跨夏令时切换的班次应按确定的实际时刻相减,不能把所有本地日都当成二十四小时。

两条实施路径:窗口分组或多范围聚合

通用思路是在清洗裁剪后,按设备、统计窗口分区,按 s、e、event_id 稳定排序。event_id 用于解除并列,清洗层应保证该组合有确定顺序。计算 MAX(e) OVER (PARTITION BY device_id, window_id ORDER BY s, e, event_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) 得到 prior_end。这里显式排除当前行,否则当前自己的结束时间会掩盖新组边界。

在下一层查询中用 CASE WHEN prior_end IS NULL OR s > prior_end THEN 1 ELSE 0 END 生成 new_group;再在下一层使用相同排序和 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 累加 new_group,得到组号。最后按设备、窗口和组号聚合 MIN(s)、MAX(e),对这些不重叠片段的差值求和。分层查询避免在同一表达式里嵌套窗口计算,也让每一步能独立抽查。

PostgreSQL 18 可用另一条路径:将每段构造为 tstzrange(s,e,'[)'),按设备及窗口使用 range_agg 聚合成多范围,再通过 unnest 展开,对每段 upper(r)-lower(r) 求和。[2][3] 构造前必须完成有限边界检查。这里聚合的是并集;range_intersect_agg 求的是所有输入共同交集,不能替代。

也不要把多范围的最早开始到最晚结束当作总停机时长。08:00—08:10 与 08:20—08:30 的并集为 20 分钟,包住两段的外壳却有 30 分钟。range_merge 返回包住输入的最小范围,会包含中间空档。[3] 保留并逐段展开多范围,才能保留恢复运行的间隔。

窗口分组便于跨引擎移植和展示中间状态,但必须核对目标方言的窗口支持;多范围方案表达紧凑,却依赖相应数据库能力。大表还需检查设备倾斜、排序资源、窗口过滤与执行计划。不能只读取 start 落在班次内的事件,否则开始更早却延伸进班次的长停机会丢失;正确筛选应考虑整个区间是否相交。

原因贡献不能直接相加,空记录也不天然等于零

在上述构造数据中,若 A、B、C 分属不同故障原因,分别按原因统计覆盖得到 60、10、40 分钟,合计仍为 110,而设备总停机为 70。这些原因贡献是可重叠的视图,不是总量的互斥组成。报表必须标明不可加总;若要饼图或占比之和为百分之百,需要额外归属政策。

可将所有起止点切成原子片段,求每段同时活动的原因集合,再采用业务认可的主因优先级或分摊规则。比如多原因等分只是分配约定,并不证明物理因果相同。应保存原始事件到片段的对应关系、政策版本及分配结果,使分配时长之和等于设备总时长,同时允许复核重叠事实。

无符合条件记录可能是正常运行,也可能是采集缺失。PostgreSQL 的许多聚合在没有输入时返回 NULL。[2] 是否显示零,应结合独立的数据完整性证据,例如采集心跳、状态序列覆盖和来源批次完成情况。不要在查询末尾无条件 COALESCE 成零,再把“没有证据”解释为“没有停机”。

用独立对照和不变量验收交付

最小反例集应包含嵌套、部分重叠、完全重复、相邻但不重叠、明显空档、跨班次、活动未结束、反向区间和两台设备同一时刻停机。本文三个区间的手工期望为 70 分钟,窗口裁剪后为 45;两个有十分钟空档的片段为 20。它们是语义推导值,上线团队仍需在实际引擎和字段类型上执行并保存结果。

小样本可以用独立的端点扫描作对照:把全部起止时刻排序,在相邻端点之间统计活动事件数,只在数量大于零时累加长度。同一时刻的增减一起处理,防止边界顺序影响结果。它与运行最大终点分组的实现不同,更容易发现嵌套处理错误;固定一分钟采样会引入舍入误差,不宜充当精确基准。

检查不变量:复制一条事件或改变输入顺序不改变并集时长;增加一个被完整包含的事件不增加时长;新增合法停机片段不能减少覆盖;任一设备的覆盖不超过其有效统计窗口;将窗口拆成不重叠子窗口后各自计算,合计应与整体一致。跨设备隔离、原因分摊守恒和相同快照重复计算也应分别通过。

最终交付除了一个分钟数,还应包含设备与窗口、计划时间版本、数据截止时刻、有效及异常记录数、合并片段、原始事件引用和计算版本。先让可信区间形成可核对的时间集合,再让报表或智能问数解释它;查询成功返回结果只是执行检查,不能代替这些业务验收。

参考资料

  1. PostgreSQL 18 — Range Types (accessed 2026-11-04)
  2. PostgreSQL 18 — Aggregate Functions: range_agg (accessed 2026-11-04)
  3. PostgreSQL 18 — Range/Multirange Functions and Operators (accessed 2026-11-04)
  4. PostgreSQL 18 — Window Functions (accessed 2026-11-04)
返回洞察
鲁ICP备2024109755号-2
可拖动移动。右键、长按或按 Shift+F10 可选择停靠位置。