摘要:本文面向工业数字化与能源管理方向的开发者和数据分析师,介绍如何基于 DolphinDB 3.x 搭建一套工业能耗数据采集与分析 pipeline。文章先拆解“能耗分析”与“工业能耗数据采集”两个核心概念,再给出电、气、汽、水四类能源的表结构设计,随后用 DolphinDB 脚本实现数据清洗、异常检测、时间序列聚合、多维分析以及数据质量监控,最后补充实战案例与落地边界。读完本文,你可以掌握从传感器接入到分钟级汇总、再到车间/产线/设备多维下钻的完整能耗分析链路。
文章目录
-
- 一、引言:为什么能耗数据值得单独治理
- 二、概念拆解:能耗分析与工业能耗数据采集
-
- 2.1 什么是能耗分析
- 2.2 什么是工业能耗数据采集
- 2.3 能耗分析与传统能源审计的区别
- 三、能耗数据的特点与分类
-
- 3.1 工业能耗数据的典型特征
- 3.2 能源类型与占比
- 3.3 分析维度
- 四、采集架构与数据结构设计
-
- 4.1 整体架构思路
- 4.2 核心表结构
- 4.3 冷热数据分离思路
- 五、数据清洗与异常检测
-
- 5.1 为什么必须先做数据质量治理
- 5.2 清洗与异常检测实现
- 5.3 数据质量维度
- 六、时间序列聚合与多维分析
-
- 6.1 从功率到能耗的换算
- 6.2 多维分析函数
- 6.3 分析维度层次图
- 七、流式聚合与质量监控
-
- 7.1 时间序列聚合引擎
- 7.2 实时流水线时序
- 八、查询接口与报告生成
-
- 8.1 常用查询接口
- 8.2 日报内容设计
- 九、实战案例:注塑车间电力能耗分析
-
- 9.1 场景描述
- 9.2 实施步骤
- 9.3 关键发现
- 十、边界、风险与落地建议
-
- 10.1 适用边界
- 10.2 常见风险与缓解措施
- 10.3 与节能项目的关系
- 十一、总结与展望
- 参考资料
一、引言:为什么能耗数据值得单独治理
在制造业运营成本中,能源费用通常仅次于原材料和人工。一家中型工厂每月电费可能达到数百万元,而燃气、蒸汽、压缩空气、工业用水同样不容小觑。问题在于,很多企业的能耗数据仍然停留在“月底抄表”阶段:月初拿到总账单,知道用了多少电,却说不清哪个车间、哪条产线、哪台设备在哪个时段用了多少。没有细粒度数据,节能优化就只能靠经验,无法量化效果。
工业能耗分析的第一步,是把分散在电表、气表、蒸汽表、水表中的数据统一采集起来,并按时间、空间、能源类型、产品等维度建模。只有数据粒度足够细、质量足够高,后续的趋势分析、异常识别、定额对标、成本分摊才有意义。DolphinDB 作为时序数据库,具备高吞吐写入、向量化计算、流式聚合与分布式存储能力,非常适合承载这类高频、多源、强时间属性的能耗数据。本文示例基于 DolphinDB 3.x(截至 2026 年 8 月)语法;若后续版本调整 createTimeSeriesEngine 等函数签名,请以官方最新文档为准。DolphinDB 2.x 用户可把本文中的流计算函数替换为对应旧版签名作为替代方案,核心的表结构设计、清洗逻辑与能耗换算方法属于经典时序分析原理,长期适用。
二、概念拆解:能耗分析与工业能耗数据采集
2.1 什么是能耗分析
能耗分析(Energy Consumption Analysis)是对企业能源使用情况进行量化、归因和优化的过程。它回答三类问题:第一,用了多少——按时间、空间、能源类型统计用量;第二,用得是否合理——通过单耗、同比环比、峰谷平占比判断效率;第三,如何改进——识别高耗能设备、优化生产排程、调整峰谷用电策略。好的能耗分析不是做一张漂亮的报表,而是形成“数据采集 → 清洗建模 → 指标计算 → 决策反馈”的闭环。
2.2 什么是工业能耗数据采集
工业能耗数据采集(Industrial Energy Data Acquisition)是指把现场计量仪表的读数实时或准实时地汇总到数据平台的过程。它通常涉及三类数据:一是瞬时值,例如功率、电压、电流、流量、压力、温度;二是累计值,例如电表累计度数、气表累计立方;三是元数据,例如仪表编号、安装位置、所属车间、能源类型、额定功率。采集时需要解决协议异构(Modbus、MQTT、OPC UA)、采样频率不一致、网络抖动导致的数据缺失、以及量纲单位不统一等问题。
2.3 能耗分析与传统能源审计的区别
传统能源审计往往是事后、静态、抽样式的:审计人员到场测量,出具报告后束之高阁。基于 DolphinDB 的能耗分析则是实时、动态、全量的:每一分钟的数据都被记录,可以按任意维度下钻,发现异常后立即告警。前者适合合规与年度评估,后者适合日常运营优化。两者结合,才能既满足管理要求,又真正驱动节能落地。
三、能耗数据的特点与分类
3.1 工业能耗数据的典型特征
工业能耗数据有三个显著特征。第一,高频:电表通常按秒级或分钟级上报功率,一天就会产生 1440 条甚至更多记录。第二,多源:电力、燃气、蒸汽、压缩空气、水可能来自不同厂家、不同协议、不同采集网关。第三,强时间属性:峰谷电价、生产班次、季节变化都会让能耗呈现明显的时间模式。因此,数据库必须同时支持高并发写入、时间序列聚合与多表关联查询。
3.2 能源类型与占比
不同能源的计量方式、单位和成本差异很大。下表列出了工业现场最常见的四类能源及其典型用途。
| 电力 | 电机、照明、空调、空压机 | kWh | 高(峰谷电价差异大) |
| 燃气 | 加热炉、干燥设备 | m³ | 中高 |
| 蒸汽 | 加热、动力、灭菌 | t | 中 |
| 压缩空气 | 气动设备、喷涂 | m³ | 中低(常被忽视) |
电力的成本敏感度最高,且峰谷平电价差异明显,因此通常是能耗分析的重点。但燃气和蒸汽的热值换算、压缩空气的管网损耗同样不可忽略,否则容易出现“省电费、浪费气”的局部优化。
3.3 分析维度
能耗分析的价值很大程度上取决于维度设计。常见的四维模型如下表所示。
| 时间维度 | 时、日、周、月、年,识别峰谷与趋势 | 某日白班用电量高于夜班 30% |
| 空间维度 | 车间、产线、设备,定位高耗能点 | 注塑车间单耗最高 |
| 能源类型 | 电、气、汽、水,横向对比 | 蒸汽成本占比持续上升 |
| 产品维度 | 单品能耗、工序能耗,支撑成本核算 | A 产品单位能耗比 B 低 12% |
这四个维度相互交叉,可以组合出大量分析视角。例如“注塑车间近一周夜班电力单耗”就同时用到了时间、空间、能源类型和产品维度。
四、采集架构与数据结构设计
4.1 整体架构思路
工业能耗采集通常采用“边缘网关 + 流式入库 + 分层汇总”的架构。现场仪表通过边缘网关把数据发送到消息队列或直接写入 DolphinDB 的流表;DolphinDB 的时间序列引擎按分钟窗口自动聚合;汇总结果写入结果表,供看板和报告查询。原始明细数据则按日期分区保存到分布式库,方便后续回溯与模型训练。
#mermaid-svg-wPSwVY32afGzH9mb{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wPSwVY32afGzH9mb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wPSwVY32afGzH9mb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wPSwVY32afGzH9mb .error-icon{fill:#552222;}#mermaid-svg-wPSwVY32afGzH9mb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wPSwVY32afGzH9mb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wPSwVY32afGzH9mb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wPSwVY32afGzH9mb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wPSwVY32afGzH9mb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wPSwVY32afGzH9mb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wPSwVY32afGzH9mb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wPSwVY32afGzH9mb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wPSwVY32afGzH9mb .marker.cross{stroke:#333333;}#mermaid-svg-wPSwVY32afGzH9mb svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wPSwVY32afGzH9mb p{margin:0;}#mermaid-svg-wPSwVY32afGzH9mb .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-wPSwVY32afGzH9mb .cluster-label text{fill:#333;}#mermaid-svg-wPSwVY32afGzH9mb .cluster-label span{color:#333;}#mermaid-svg-wPSwVY32afGzH9mb .cluster-label span p{background-color:transparent;}#mermaid-svg-wPSwVY32afGzH9mb .label text,#mermaid-svg-wPSwVY32afGzH9mb span{fill:#333;color:#333;}#mermaid-svg-wPSwVY32afGzH9mb .node rect,#mermaid-svg-wPSwVY32afGzH9mb .node circle,#mermaid-svg-wPSwVY32afGzH9mb .node ellipse,#mermaid-svg-wPSwVY32afGzH9mb .node polygon,#mermaid-svg-wPSwVY32afGzH9mb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wPSwVY32afGzH9mb .rough-node .label text,#mermaid-svg-wPSwVY32afGzH9mb .node .label text,#mermaid-svg-wPSwVY32afGzH9mb .image-shape .label,#mermaid-svg-wPSwVY32afGzH9mb .icon-shape .label{text-anchor:middle;}#mermaid-svg-wPSwVY32afGzH9mb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wPSwVY32afGzH9mb .rough-node .label,#mermaid-svg-wPSwVY32afGzH9mb .node .label,#mermaid-svg-wPSwVY32afGzH9mb .image-shape .label,#mermaid-svg-wPSwVY32afGzH9mb .icon-shape .label{text-align:center;}#mermaid-svg-wPSwVY32afGzH9mb .node.clickable{cursor:pointer;}#mermaid-svg-wPSwVY32afGzH9mb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wPSwVY32afGzH9mb .arrowheadPath{fill:#333333;}#mermaid-svg-wPSwVY32afGzH9mb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wPSwVY32afGzH9mb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wPSwVY32afGzH9mb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wPSwVY32afGzH9mb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wPSwVY32afGzH9mb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wPSwVY32afGzH9mb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wPSwVY32afGzH9mb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wPSwVY32afGzH9mb .cluster text{fill:#333;}#mermaid-svg-wPSwVY32afGzH9mb .cluster span{color:#333;}#mermaid-svg-wPSwVY32afGzH9mb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wPSwVY32afGzH9mb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wPSwVY32afGzH9mb rect.text{fill:none;stroke-width:0;}#mermaid-svg-wPSwVY32afGzH9mb .icon-shape,#mermaid-svg-wPSwVY32afGzH9mb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wPSwVY32afGzH9mb .icon-shape p,#mermaid-svg-wPSwVY32afGzH9mb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wPSwVY32afGzH9mb .icon-shape .label rect,#mermaid-svg-wPSwVY32afGzH9mb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wPSwVY32afGzH9mb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wPSwVY32afGzH9mb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wPSwVY32afGzH9mb :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
电表/气表/蒸汽表/水表
边缘网关
energy_stream 流表
时间序列聚合引擎
energy_summary 汇总表
监控看板
日报/周报
dfs://energy_db 历史明细

4.2 核心表结构
下面的代码创建了能耗采集所需的三张核心表:原始数据流表、仪表元数据表、分钟级汇总结果表,同时建立按日期分区的分布式库保存历史明细。流表用于实时接入,结果表用于快速查询,分布式库用于长期归档。
// 1. 原始数据流表:按分钟接入电表/气表/蒸汽表/水表
share streamTable(1000000:0,
`meter_id`timestamp`energy_type`power`voltage`current`power_factor,
[SYMBOL, TIMESTAMP, STRING, DOUBLE, DOUBLE, DOUBLE, DOUBLE]) as energy_stream
enableTablePersistence(energy_stream, true, true, 10000000)
// 2. 仪表元数据表
share table(1:0,
`meter_id`meter_name`meter_type`location`device_id`rated_power,
[SYMBOL, STRING, STRING, STRING, SYMBOL, DOUBLE]) as meter_info
// 3. 分钟级汇总结果表
share table(1:0,
`time_window`meter_id`energy_type`total_energy`avg_power`max_power`min_power,
[TIMESTAMP, SYMBOL, STRING, DOUBLE, DOUBLE, DOUBLE, DOUBLE]) as energy_summary
// 4. 分布式分区库(按日期分区,保存历史明细)
// 日期范围可按实际业务扩展,VALUE 分区便于按天裁剪
db = database("dfs://energy_db", VALUE, 2024.01.01..2026.12.31)
schema = table(1:0,
`meter_id`timestamp`energy_type`power`voltage`current`power_factor,
[SYMBOL, TIMESTAMP, STRING, DOUBLE, DOUBLE, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `energy_data, `timestamp)
代码解释:energy_stream 是数据入口,使用 streamTable 保证高并发写入;enableTablePersistence 开启持久化,避免节点重启后数据全部丢失。meter_info 保存仪表元数据,用于后续按车间、产线、设备下钻。energy_summary 是聚合结果表,按分钟窗口存储能耗总量与平均/最大/最小功率。dfs://energy_db 按日期分区,适合保存数年的明细数据, partition 范围可以根据实际业务调整。
4.3 冷热数据分离思路
在能耗场景中,实时看板和告警主要访问最近几天的汇总数据,而历史明细更多用于审计、回测和模型训练。把热数据放在 energy_summary 等内存或高频访问表中,把冷数据放到按日期分区的 dfs://energy_db 中,可以兼顾查询性能与存储成本。DolphinDB 支持对分区表做时间范围裁剪,查询某几天的明细时只会扫描对应分区,避免全表扫描。同时,可以把过期分区归档到对象存储,进一步降低长期保存成本。
五、数据清洗与异常检测
5.1 为什么必须先做数据质量治理
现场采集的原始数据往往存在三类问题:一是异常值,例如功率突然跳变到上百万,通常是通讯干扰;二是缺失值,网络抖动导致某分钟没有上报;三是重复值,边缘网关重传导致同一时刻出现多条记录。如果不先清洗,聚合结果会出现尖峰或缺口,直接影响能耗统计与异常告警的可信度。
5.2 清洗与异常检测实现
下面的代码实现了清洗和前向填充两个步骤,并基于 3σ 准则检测异常功率点。这里的阈值是示例值,生产环境应根据现场额定功率和工艺范围调整。
def preprocessEnergyData(data) {
// 清洗:功率、电压、电流必须在合理区间
cleaned = select * from data
where power >= 0 and power < 100000
and voltage >= 200 and voltage <= 500
and current >= 0 and current < 1000
// 前值填充:按仪表分区,用上一分钟值补缺失
processed = select meter_id, timestamp, energy_type,
ffill(power) as power,
ffill(voltage) as voltage,
ffill(current) as current,
ffill(power_factor) as power_factor
from cleaned context by meter_id
return processed
}
def detectAnomalies(data) {
stats = select meter_id, avg(power) as mean, std(power) as std
from data group by meter_id
anomalies = select d.meter_id, d.timestamp, d.power
from data d
left join stats s on d.meter_id = s.meter_id
where d.power > s.mean + 3 * s.std
or d.power < s.mean – 3 * s.std
return anomalies
}
代码解释:preprocessEnergyData 先用范围过滤剔除明显异常的原始值,再用 ffill 按 meter_id 分区做前向填充,保证每条时间序列在缺失点仍保留可用值。detectAnomalies 则按仪表分组计算功率均值与标准差,把偏离均值超过 3 倍标准差的点标记为异常。需要注意的是,3σ 方法假设功率近似正态分布,对于启停频繁的设备,应改用基于工况的分段基线或基于模型的残差检测。
5.3 数据质量维度
数据质量监控可以从完整性、准确性、及时性三个维度展开。
| 完整性 | 实际记录数 / 期望记录数 | 缺失率低于 10% |
| 准确性 | 落在合理区间的记录占比 | 异常值比例低于 2% |
| 及时性 | 最近数据距当前时间间隔 | 延迟不超过 5 分钟 |
这三个维度可以组合成一个综合质量分,用于在仪表盘上提示“数据可信度”。如果质量分偏低,分析结果应谨慎解读,并优先排查采集链路。
六、时间序列聚合与多维分析
6.1 从功率到能耗的换算
电表通常上报的是功率(kW),而我们需要的是能耗(kWh)。如果采样频率是每分钟一次,那么 1 分钟内的能耗近似等于功率平均值除以 60。createTimeSeriesEngine 可以按固定时间窗口自动计算 sum(power) / 60,并把结果写入 energy_summary。这种换算方式在分钟级粒度下误差很小,但如果采样间隔不稳定,则需要用梯形积分或累计电表读数差分来替代。
6.2 多维分析函数
下面的代码展示了时间、空间、能源类型三种最常用的分析函数。时间维度支持小时、日、月三种粒度;空间维度通过 meter_info 关联得到车间或产线;能源类型维度则直接按类型汇总。
def analyzeByTime(data, timeUnit) {
return iif(timeUnit == "hour",
select bar(timestamp, 1h) as hour, sum(power) / 60 as total_energy,
avg(power) as avg_power, max(power) as max_power
from data group by bar(timestamp, 1h),
iif(timeUnit == "day",
select date(timestamp) as date, sum(power) / 60 / 1000 as total_kwh,
avg(power) as avg_power from data group by date(timestamp),
select month(timestamp) as month, sum(power) / 60 / 1000 as total_kwh
from data group by month(timestamp)))
}
def analyzeByLocation(data, meterInfo) {
return select m.location, sum(e.power) / 60 as total_energy,
avg(e.power) as avg_power
from data e
left join meterInfo m on e.meter_id = m.meter_id
group by m.location
}
def analyzeByEnergyType(data) {
return select energy_type, sum(power) / 60 as total_energy,
avg(power) as avg_power, count(*) as sample_count
from data group by energy_type
}
代码解释:analyzeByTime 用 iif 分支实现三种时间粒度,避免了为每种粒度写一个独立函数;bar(timestamp, 1h) 把任意时间对齐到小时窗口,是 DolphinDB 中常用的时间对齐函数。analyzeByLocation 通过 left join 把原始数据与 meter_info 关联,实现从“仪表”到“车间/产线”的汇总。analyzeByEnergyType 则直接按能源类型分组,适合做多能源成本占比分析。实际项目中,还可以把这三个函数的结果再与生产产量关联,计算单位产品能耗。
6.3 分析维度层次图
下图展示了时间、空间、能源类型、产品四个维度如何从不同角度切割同一批能耗数据。
#mermaid-svg-m1dD2ByM6Oi8K5YY{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-m1dD2ByM6Oi8K5YY .error-icon{fill:#552222;}#mermaid-svg-m1dD2ByM6Oi8K5YY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-m1dD2ByM6Oi8K5YY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .marker.cross{stroke:#333333;}#mermaid-svg-m1dD2ByM6Oi8K5YY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-m1dD2ByM6Oi8K5YY p{margin:0;}#mermaid-svg-m1dD2ByM6Oi8K5YY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster-label text{fill:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster-label span{color:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster-label span p{background-color:transparent;}#mermaid-svg-m1dD2ByM6Oi8K5YY .label text,#mermaid-svg-m1dD2ByM6Oi8K5YY span{fill:#333;color:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .node rect,#mermaid-svg-m1dD2ByM6Oi8K5YY .node circle,#mermaid-svg-m1dD2ByM6Oi8K5YY .node ellipse,#mermaid-svg-m1dD2ByM6Oi8K5YY .node polygon,#mermaid-svg-m1dD2ByM6Oi8K5YY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .rough-node .label text,#mermaid-svg-m1dD2ByM6Oi8K5YY .node .label text,#mermaid-svg-m1dD2ByM6Oi8K5YY .image-shape .label,#mermaid-svg-m1dD2ByM6Oi8K5YY .icon-shape .label{text-anchor:middle;}#mermaid-svg-m1dD2ByM6Oi8K5YY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .rough-node .label,#mermaid-svg-m1dD2ByM6Oi8K5YY .node .label,#mermaid-svg-m1dD2ByM6Oi8K5YY .image-shape .label,#mermaid-svg-m1dD2ByM6Oi8K5YY .icon-shape .label{text-align:center;}#mermaid-svg-m1dD2ByM6Oi8K5YY .node.clickable{cursor:pointer;}#mermaid-svg-m1dD2ByM6Oi8K5YY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .arrowheadPath{fill:#333333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m1dD2ByM6Oi8K5YY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-m1dD2ByM6Oi8K5YY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m1dD2ByM6Oi8K5YY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster text{fill:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY .cluster span{color:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-m1dD2ByM6Oi8K5YY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-m1dD2ByM6Oi8K5YY rect.text{fill:none;stroke-width:0;}#mermaid-svg-m1dD2ByM6Oi8K5YY .icon-shape,#mermaid-svg-m1dD2ByM6Oi8K5YY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m1dD2ByM6Oi8K5YY .icon-shape p,#mermaid-svg-m1dD2ByM6Oi8K5YY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-m1dD2ByM6Oi8K5YY .icon-shape .label rect,#mermaid-svg-m1dD2ByM6Oi8K5YY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m1dD2ByM6Oi8K5YY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-m1dD2ByM6Oi8K5YY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-m1dD2ByM6Oi8K5YY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
能耗数据
时间维度
空间维度
能源类型
产品维度
时/日/周/月
车间/产线/设备
电/气/汽/水
单品能耗/工序能耗
七、流式聚合与质量监控
7.1 时间序列聚合引擎
DolphinDB 的 createTimeSeriesEngine 是流式聚合的核心组件。它订阅流表,按指定窗口自动计算聚合指标,并把结果写入目标表。对于能耗场景,窗口通常设为 1 分钟或 15 分钟,聚合指标包括总能耗、平均功率、最大/最小功率。
// 创建时间序列聚合引擎,按 1 分钟窗口汇总
aggEngine = createTimeSeriesEngine("energy_agg", 60000,
<[sum(power) / 60 as total_energy,
avg(power) as avg_power,
max(power) as max_power,
min(power) as min_power]>,
energy_summary, `timestamp, `meter_id)
subscribeTable(, "energy_stream", "agg", -1, aggEngine, true)
// 数据质量监控任务
def qualityMonitorTask() {
while (true) {
t = now()
data = select * from energy_stream where timestamp > t – 300000
completeness = data.rows() * 1.0 / 1440
accuracy = exec count(*) * 1.0 / data.rows() from data
where power >= 0 and power < 100000
if (completeness < 0.9) print("告警:数据完整性不足 " + string(completeness * 100) + "%")
sleep(300000)
}
}
submitJob("quality_monitor", "数据质量监控", qualityMonitorTask)
代码解释:createTimeSeriesEngine 的五个关键参数分别是引擎名称、窗口大小(毫秒)、聚合表达式、输出表、时间列和分组列。这里窗口设为 60000 毫秒,即 1 分钟;分组列是 meter_id,意味着每台仪表都会独立聚合。subscribeTable 把引擎与 energy_stream 绑定,数据写入流表后会自动触发聚合。qualityMonitorTask 每 5 分钟检查一次最近 5 分钟的数据完整性,若低于 90% 则打印告警;accuracy 通过合理区间过滤计算正常记录占比,两个指标都可以进一步写入质量监控表,供看板展示。
7.2 实时流水线时序
下图展示了从传感器写入流表,到聚合引擎生成汇总结果,再到质量监控任务与看板消费的完整时序。
监控看板
质量监控任务
energy_summary
时间序列聚合引擎
energy_stream
电表/气表/PLC
监控看板
质量监控任务
energy_summary
时间序列聚合引擎
energy_stream
电表/气表/PLC
#mermaid-svg-aF9GpyE2LoNLZTWi{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aF9GpyE2LoNLZTWi .error-icon{fill:#552222;}#mermaid-svg-aF9GpyE2LoNLZTWi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aF9GpyE2LoNLZTWi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aF9GpyE2LoNLZTWi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aF9GpyE2LoNLZTWi .marker.cross{stroke:#333333;}#mermaid-svg-aF9GpyE2LoNLZTWi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aF9GpyE2LoNLZTWi p{margin:0;}#mermaid-svg-aF9GpyE2LoNLZTWi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aF9GpyE2LoNLZTWi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aF9GpyE2LoNLZTWi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-aF9GpyE2LoNLZTWi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-aF9GpyE2LoNLZTWi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-aF9GpyE2LoNLZTWi .sequenceNumber{fill:white;}#mermaid-svg-aF9GpyE2LoNLZTWi #sequencenumber{fill:#333;}#mermaid-svg-aF9GpyE2LoNLZTWi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-aF9GpyE2LoNLZTWi .messageText{fill:#333;stroke:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aF9GpyE2LoNLZTWi .labelText,#mermaid-svg-aF9GpyE2LoNLZTWi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .loopText,#mermaid-svg-aF9GpyE2LoNLZTWi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aF9GpyE2LoNLZTWi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-aF9GpyE2LoNLZTWi .noteText,#mermaid-svg-aF9GpyE2LoNLZTWi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-aF9GpyE2LoNLZTWi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aF9GpyE2LoNLZTWi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aF9GpyE2LoNLZTWi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aF9GpyE2LoNLZTWi .actorPopupMenu{position:absolute;}#mermaid-svg-aF9GpyE2LoNLZTWi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-aF9GpyE2LoNLZTWi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aF9GpyE2LoNLZTWi .actor-man circle,#mermaid-svg-aF9GpyE2LoNLZTWi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-aF9GpyE2LoNLZTWi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
loop
[每 5 分钟]
写入功率/电压/电流
触发分钟级窗口聚合
写入 total_energy/avg_power
查询最近 5 分钟数据
计算完整率与准确率
查询能耗汇总与趋势
渲染看板与告警

八、查询接口与报告生成
8.1 常用查询接口
能耗系统的最终用户通常关心三类查询:单表在某段时间内的能耗明细、按能源类型统计某日总量、以及全厂 TOP N 高耗能仪表。下面的函数把这些查询封装为 addFunctionView,方便前端或 BI 工具直接调用。
def getEnergySummary(meterId, startTime, endTime) {
return select * from energy_summary
where meter_id = meterId
and time_window between startTime and endTime
order by time_window
}
def getEnergyByType(energyType, date) {
return select meter_id, sum(total_energy) as total
from energy_summary
where energy_type = energyType
and date(time_window) = date
group by meter_id
}
def getEnergyRank(topN = 10) {
return select meter_id, sum(total_energy) as total
from energy_summary
where time_window > now() – 86400000
group by meter_id
order by total desc
limit topN
}
addFunctionView(getEnergySummary)
addFunctionView(getEnergyByType)
addFunctionView(getEnergyRank)
代码解释:getEnergySummary 返回单表的时间序列,适合做趋势图;getEnergyByType 按能源类型汇总某日各表用量,适合做饼图或柱状图;getEnergyRank 返回最近 24 小时能耗最高的 topN 仪表,适合用于“重点关注设备”列表。addFunctionView 把这些函数发布为服务接口后,前端可以通过 DolphinDB 的 HTTP API 或 WebSocket 直接调用,无需额外后端服务。
8.2 日报内容设计
一份实用的能耗日报通常包含以下内容:昨日总能耗、同比/环比变化、各车间/能源类型占比、TOP 5 高耗能设备、异常数据条数、以及改进建议。日报的生成可以复用上面的查询函数,结果写入邮件或企业微信消息模板。建议把日报发送给设备管理部、生产计划部和财务部,让能耗数据真正进入决策流程。
九、实战案例:注塑车间电力能耗分析
9.1 场景描述
某注塑车间有 8 台注塑机,每台机器都安装了电力仪表,每分钟上报功率。车间管理员希望回答两个问题:一是哪台机器单耗最高,二是夜班和白班的用电模式是否存在明显差异。现场还发现偶尔有功率瞬间归零的异常点,需要清洗后再分析。
9.2 实施步骤
第一步,在 DolphinDB 中创建 energy_stream 和 energy_summary,并启动 1 分钟时间序列聚合引擎。第二步,把 8 台注塑机的仪表元数据写入 meter_info,包括设备编号、所属产线、额定功率。第三步,编写数据清洗逻辑,把功率为负或超过额定功率 2 倍的记录过滤掉,并用 ffill 填充缺失。第四步,用 analyzeByTime 分别按小时、日、月统计能耗,并用 analyzeByLocation 按产线对比。第五步,把结果接入 Grafana,配置“单机能耗排名”“班次对比”“异常点标记”三个看板。
9.3 关键发现
分析结果显示,3 号注塑机的日平均单耗比其余 7 台高出约 18%,且白班 10:00-11:00 经常出现功率尖峰。进一步排查发现,该机器在更换模具后加热圈参数未及时调整,导致预热阶段功耗偏高。调整参数后,单耗下降 12%,每月可节省电费约 2 万元。这个案例说明,细粒度能耗分析不仅能定位高耗能设备,还能发现具体的工艺优化点。
十、边界、风险与落地建议
10.1 适用边界
本文介绍的采集与分析方法适用于具备稳定计量仪表、数据以分钟级或更高频率上报的工业场景,例如注塑、机加、钢铁、化工等行业。它不适用于只有月度抄表数据的场景,也不适用于能源类型复杂、需要热值精确换算的场合(如多种燃气混合燃烧)。此外,如果现场网络极不稳定,缺失率长期超过 30%,应先解决采集链路问题,再上线分析功能。
10.2 常见风险与缓解措施
| 采样频率不一致 | 不同仪表上报周期不同,聚合结果失真 | 统一窗口对齐,缺失记录显式标记 |
| 量纲混淆 | 功率与能耗单位混用,导致成本计算错误 | 统一用 kWh 作为能耗单位 |
| 阈值过严 | 大量正常启停被误判为异常 | 引入工况标签,分段设置基线 |
| 数据孤岛 | 能耗数据与产量、工单数据未打通 | 建立设备级主数据,统一 device_id |
10.3 与节能项目的关系
能耗分析不是节能项目本身,而是节能项目的数据基础。它能帮你找到“哪里浪费”,但“怎么省”还需要工艺改造、设备升级、生产排程优化等具体措施。建议把能耗分析与节能 KPI 绑定:例如设定“单位产品能耗下降 5%”的目标,并通过看板持续追踪进度。只有形成“数据 → 决策 → 行动 → 再验证”的闭环,能耗系统才能真正产生经济效益。
十一、总结与展望
本文围绕 DolphinDB 能耗分析,从能耗数据特点、采集架构、表结构设计、数据清洗、异常检测、时间序列聚合、多维分析到质量监控,梳理了工业能耗数据采集分析链路。核心要点:第一,能耗分析必须先治理数据质量,否则报表不可信;第二,DolphinDB 的流表与时间序列引擎适合分钟级能耗聚合,能把高频原始数据压缩为可查询的汇总指标;第三,时间、空间、能源类型、产品四个维度的组合分析,是定位浪费点和量化节能效果的关键。
最大挑战往往不是技术实现,而是计量覆盖率与业务闭环。很多工厂只在一级总表部署电表,车间级和设备级数据缺失,分析只能停留在整体层面。建议分阶段推进:先接入关键高耗能设备,跑通数据清洗与日报;再扩展仪表覆盖,引入定额对标;最后结合生产数据做单品能耗分析,支撑精细化管理。未来可关联碳排放因子生成碳排放报告,支撑绿色制造。
思考题:
参考资料
- DolphinDB 官方文档
- DolphinDB 流计算概览
- DolphinDB 函数参考
- ISO 50001 能源管理体系
- U.S. DOE 工业能源管理指南




