DolphinDB 能耗统计分析实战:报表生成、同比环比与定额对比

摘要:能耗统计分析是企业节能降碳与精益生产的决策基础。本文基于 DolphinDB 3.x,系统讲解如何利用时序聚合、窗口函数与函数视图,构建覆盖日报、周报、月报、年报的能耗报表体系,并实现同比环比、设备能耗排名、能耗强度计算与定额对比。全文给出可直接运行的 DolphinDB 脚本、可视化设计思路与实施风险提醒,帮助读者把分散的原始测点转化为可指导行动的管理报表,适合工业互联网、智能制造领域的开发者与数据工程师参考。

文章目录

    • 一、引言:为什么能耗统计分析是节能管理的核心
    • [二、概念拆解:报表、同比环比、能耗强度与 DolphinDB](#二、概念拆解:报表、同比环比、能耗强度与 DolphinDB)
      • [2.1 什么是能耗报表](#2.1 什么是能耗报表)
      • [2.2 什么是同比与环比](#2.2 什么是同比与环比)
      • [2.3 什么是能耗强度与定额](#2.3 什么是能耗强度与定额)
      • [2.4 为什么选 DolphinDB 做能耗统计](#2.4 为什么选 DolphinDB 做能耗统计)
    • 三、报表体系设计
      • [3.1 报表类型与维度](#3.1 报表类型与维度)
      • [3.2 核心指标定义](#3.2 核心指标定义)
      • [3.3 报表生成流水线](#3.3 报表生成流水线)
      • [3.4 报表周期与适用场景](#3.4 报表周期与适用场景)
      • [3.5 报表数据质量管理](#3.5 报表数据质量管理)
    • [四、核心实现:DolphinDB 报表与对比分析](#四、核心实现:DolphinDB 报表与对比分析)
      • [4.1 报表结果表与峰谷平函数](#4.1 报表结果表与峰谷平函数)
      • [4.2 日报与月报生成](#4.2 日报与月报生成)
      • [4.3 同比、环比与趋势分析](#4.3 同比、环比与趋势分析)
      • [4.4 能耗排名与强度计算](#4.4 能耗排名与强度计算)
      • [4.5 报表调度与查询接口](#4.5 报表调度与查询接口)
    • 五、可视化与报表呈现
      • [5.1 报表可视化设计](#5.1 报表可视化设计)
      • [5.2 图表选型建议](#5.2 图表选型建议)
      • [5.3 报表导出与分享](#5.3 报表导出与分享)
      • [5.4 报表权限与数据隔离](#5.4 报表权限与数据隔离)
    • 六、边界条件与实施风险
      • [6.1 数据口径一致性风险](#6.1 数据口径一致性风险)
      • [6.2 缺失数据与异常值处理](#6.2 缺失数据与异常值处理)
      • [6.3 定额制定的科学性](#6.3 定额制定的科学性)
      • [6.4 计算性能边界](#6.4 计算性能边界)
      • [6.5 与碳排放核算的衔接](#6.5 与碳排放核算的衔接)
    • 七、总结
    • 参考链接

一、引言:为什么能耗统计分析是节能管理的核心

在工业生产中,能耗成本往往仅次于原材料与人工。很多企业已经部署了电表、气表、水表,能够采集到海量的实时数据,但如果缺少系统的统计分析,这些数据只能停留在"看得见"的层面,无法转化为"管得住"的行动。能耗统计分析的价值,正是把分散的原始测点聚合为日、周、月、年的报表,并通过同比、环比、排名、强度等多维对比,帮助管理者识别浪费点、评估节能措施效果、制定定额目标。

与传统 BI 报表不同,工业能耗报表具有鲜明的时序特征:数据按时间连续产生,统计周期固定,维度组合复杂,且对计算性能要求较高。当设备数量达到数千台时,日报生成如果依赖离线批处理,可能需要数小时才能完成,无法满足次日一早的管理需求。DolphinDB 的向量化计算与分区存储能力,可以把这一过程压缩到分钟级,显著提升管理响应速度与决策效率。DolphinDB 作为时序数据库,凭借向量化计算、分区存储与内置的时间序列函数,可以把报表生成时间压缩到分钟级甚至秒级。

本文所有代码示例均基于 DolphinDB 3.x 编写并验证。如果你使用 DolphinDB 2.x,核心的 sumavgmavgbarmonth 等聚合函数保持一致,但 enableTablePersistenceaddFunctionView 等少数函数的默认参数可能略有不同,建议参考对应版本的官方文档微调。文中涉及的报表设计思想、同比环比方法与能耗强度计算属于管理会计与工业工程领域的经典方法,长期适用,不随工具版本变化而失效。


二、概念拆解:报表、同比环比、能耗强度与 DolphinDB

2.1 什么是能耗报表

能耗报表是按固定周期对能耗数据进行汇总、分类与呈现的结果。常见周期包括日报、周报、月报和年报;常见维度包括时间、空间、能源类型和产品。一份好的报表不仅要回答"用了多少电",还要回答"什么时候用的""哪个设备用的""与上期相比如何"。因此,报表设计本质上是一个"指标 + 维度 + 周期"的三维建模问题。

2.2 什么是同比与环比

同比(Year-over-Year,YoY)是与去年同期相比,用于消除季节性波动的影响,判断长期趋势;环比(Month-over-Month,MoM)是与上一统计周期相比,用于捕捉短期变化。在能耗分析中,同比适合评估年度节能目标完成情况,环比适合发现突增突降。计算时需要注意口径一致:比较的两个周期应当包含相似的生产负荷、工作日天数与工艺条件,否则结论会失真。

2.3 什么是能耗强度与定额

能耗强度通常指单位产品能耗或单位产值能耗,是衡量能源利用效率的核心指标。定额则是企业根据历史数据、工艺标准与节能目标制定的能耗上限。通过"实际能耗强度 vs 定额"的对比,可以快速判断某台设备或某条产线是否超标。需要注意的是,定额应当动态调整:新产品、新设备、新工艺的引入都会改变合理的能耗基线。

2.4 为什么选 DolphinDB 做能耗统计

DolphinDB 在能耗统计场景中有四项优势:一是内置丰富的时间序列函数,如 barmonthmavgrank,可以简化报表 SQL;二是支持按时间分区存储,历史数据查询效率高;三是函数视图可以直接暴露报表接口,减少后端开发;四是支持定时任务 submitJob,可以实现报表的自动调度。对于已经使用其他时序数据库的企业,DolphinDB 也可以作为报表聚合层的替代方案,而不必完全替换现有采集链路。


三、报表体系设计

3.1 报表类型与维度

一套完整的能耗报表体系,应当覆盖从宏观到微观、从短期到长期的多个视角。下图展示了常见报表类型之间的层级关系:日报支撑周报的明细,周报支撑月报的趋势,月报支撑年报的战略决策。
#mermaid-svg-kvgBD9oDN9mjaw1v{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-kvgBD9oDN9mjaw1v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kvgBD9oDN9mjaw1v .error-icon{fill:#552222;}#mermaid-svg-kvgBD9oDN9mjaw1v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kvgBD9oDN9mjaw1v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kvgBD9oDN9mjaw1v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kvgBD9oDN9mjaw1v .marker.cross{stroke:#333333;}#mermaid-svg-kvgBD9oDN9mjaw1v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kvgBD9oDN9mjaw1v p{margin:0;}#mermaid-svg-kvgBD9oDN9mjaw1v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster-label text{fill:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster-label span{color:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster-label span p{background-color:transparent;}#mermaid-svg-kvgBD9oDN9mjaw1v .label text,#mermaid-svg-kvgBD9oDN9mjaw1v span{fill:#333;color:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v .node rect,#mermaid-svg-kvgBD9oDN9mjaw1v .node circle,#mermaid-svg-kvgBD9oDN9mjaw1v .node ellipse,#mermaid-svg-kvgBD9oDN9mjaw1v .node polygon,#mermaid-svg-kvgBD9oDN9mjaw1v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kvgBD9oDN9mjaw1v .rough-node .label text,#mermaid-svg-kvgBD9oDN9mjaw1v .node .label text,#mermaid-svg-kvgBD9oDN9mjaw1v .image-shape .label,#mermaid-svg-kvgBD9oDN9mjaw1v .icon-shape .label{text-anchor:middle;}#mermaid-svg-kvgBD9oDN9mjaw1v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kvgBD9oDN9mjaw1v .rough-node .label,#mermaid-svg-kvgBD9oDN9mjaw1v .node .label,#mermaid-svg-kvgBD9oDN9mjaw1v .image-shape .label,#mermaid-svg-kvgBD9oDN9mjaw1v .icon-shape .label{text-align:center;}#mermaid-svg-kvgBD9oDN9mjaw1v .node.clickable{cursor:pointer;}#mermaid-svg-kvgBD9oDN9mjaw1v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kvgBD9oDN9mjaw1v .arrowheadPath{fill:#333333;}#mermaid-svg-kvgBD9oDN9mjaw1v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kvgBD9oDN9mjaw1v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kvgBD9oDN9mjaw1v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kvgBD9oDN9mjaw1v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kvgBD9oDN9mjaw1v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kvgBD9oDN9mjaw1v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster text{fill:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v .cluster span{color:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v 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-kvgBD9oDN9mjaw1v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kvgBD9oDN9mjaw1v rect.text{fill:none;stroke-width:0;}#mermaid-svg-kvgBD9oDN9mjaw1v .icon-shape,#mermaid-svg-kvgBD9oDN9mjaw1v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kvgBD9oDN9mjaw1v .icon-shape p,#mermaid-svg-kvgBD9oDN9mjaw1v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kvgBD9oDN9mjaw1v .icon-shape .label rect,#mermaid-svg-kvgBD9oDN9mjaw1v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kvgBD9oDN9mjaw1v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kvgBD9oDN9mjaw1v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kvgBD9oDN9mjaw1v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始功率数据
日报
周报
月报
年报
设备日能耗明细
周环比分析
月度同比分析
年度能耗总览
报表中心

3.2 核心指标定义

指标定义是报表准确性的前提。同一个词在不同场景下可能有不同口径,例如"平均功率"可以是日内所有采样点的算术平均,也可以是按小时加权平均。下表列出了本文使用的核心指标及其计算方式。

指标名称 业务含义 计算方式 常用维度
总能耗 统计周期内累计用电量 sum(power) / 60 日/周/月/年
平均功率 周期内平均负荷 avg(power) 日/小时
最大需量 周期内最大功率需求 max(power) 日/月
负荷率 平均功率占最大需量比例 avg(power) / max(power) * 100
峰时占比 峰时电量占总电量比例 peak_energy / total_energy * 100 日/月
能耗强度 单位产品能耗 energy / production_qty 月/产品

3.3 报表生成流水线

下图展示了从原始数据到最终报表的自动化流程。采集网关持续写入流表,定时任务在每日 0 点触发日报生成,每月 1 日触发月报生成,计算结果落入分区库,前端通过函数视图查询展示。
报表前端 函数视图 monthly_report daily_report 报表调度任务 power_stream 采集网关 报表前端 函数视图 monthly_report daily_report 报表调度任务 power_stream 采集网关 #mermaid-svg-sPFBTtNdFr5Oqzpf{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-sPFBTtNdFr5Oqzpf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sPFBTtNdFr5Oqzpf .error-icon{fill:#552222;}#mermaid-svg-sPFBTtNdFr5Oqzpf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sPFBTtNdFr5Oqzpf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sPFBTtNdFr5Oqzpf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sPFBTtNdFr5Oqzpf .marker.cross{stroke:#333333;}#mermaid-svg-sPFBTtNdFr5Oqzpf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sPFBTtNdFr5Oqzpf p{margin:0;}#mermaid-svg-sPFBTtNdFr5Oqzpf .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sPFBTtNdFr5Oqzpf text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sPFBTtNdFr5Oqzpf .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-sPFBTtNdFr5Oqzpf .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-sPFBTtNdFr5Oqzpf #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-sPFBTtNdFr5Oqzpf .sequenceNumber{fill:white;}#mermaid-svg-sPFBTtNdFr5Oqzpf #sequencenumber{fill:#333;}#mermaid-svg-sPFBTtNdFr5Oqzpf #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-sPFBTtNdFr5Oqzpf .messageText{fill:#333;stroke:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sPFBTtNdFr5Oqzpf .labelText,#mermaid-svg-sPFBTtNdFr5Oqzpf .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .loopText,#mermaid-svg-sPFBTtNdFr5Oqzpf .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .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-sPFBTtNdFr5Oqzpf .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-sPFBTtNdFr5Oqzpf .noteText,#mermaid-svg-sPFBTtNdFr5Oqzpf .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-sPFBTtNdFr5Oqzpf .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sPFBTtNdFr5Oqzpf .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sPFBTtNdFr5Oqzpf .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sPFBTtNdFr5Oqzpf .actorPopupMenu{position:absolute;}#mermaid-svg-sPFBTtNdFr5Oqzpf .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-sPFBTtNdFr5Oqzpf .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sPFBTtNdFr5Oqzpf .actor-man circle,#mermaid-svg-sPFBTtNdFr5Oqzpf line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-sPFBTtNdFr5Oqzpf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每日 0 点 loop 每月 1 日 0 点 持续写入功率数据 读取昨日数据 生成日报 读取上月数据 生成月报 查询日报/月报/对比 读取聚合结果 返回 JSON/CSV

3.4 报表周期与适用场景

不同周期的报表服务于不同层级的管理决策。日报用于现场班组交接班与异常定位,周报用于车间周例会复盘,月报用于部门能耗考核,年报则用于企业战略层面的节能目标评估。下表给出了各周期的典型问题与推荐输出形式。

报表周期 服务对象 典型问题 推荐输出形式
日报 班组/设备运维 昨日哪台设备用电异常? 表格 + 峰谷平堆叠图
周报 车间主任 本周能耗较上周变化多少? 表格 + 环比柱状图
月报 能源管理部门 月度节能目标是否达成? 表格 + 同比曲线
年报 高层管理 全年能耗结构与趋势如何? PPT/PDF 汇总报告

3.5 报表数据质量管理

报表的可信度取决于原始数据质量。在生成报表之前,应当建立一套数据质量校验规则:剔除负值与超量程的功率点;对缺失时段标记为"数据缺失"而不是用零值填充;对重复时间戳做去重处理;对设备启停状态进行校验,避免停机期间仍产生非零功率。DolphinDB 可以通过 isNullisValiddistinct 等函数在聚合前完成清洗。建议在日报表中增加 data_completeness 字段,记录当日有效采样点占比,便于读者判断报表可信度。


四、核心实现:DolphinDB 报表与对比分析

4.1 报表结果表与峰谷平函数

第一段代码创建日报、月报结果表,并定义峰谷平时段判断函数。结果表按周期存储汇总指标,避免每次查询都扫描原始秒级数据;峰谷平函数复用实时监控中的时段定义,保证报表与大屏口径一致。

python 复制代码
// 报表结果表(日报、月报)
share table(1:0,
    `date`meter_id`total_energy`peak_energy`valley_energy`flat_energy`max_power`avg_power`load_rate,
    [DATE, SYMBOL, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]) as daily_report

share table(1:0,
    `month`meter_id`total_energy`avg_daily`peak_ratio`valley_ratio,
    [MONTH, SYMBOL, DOUBLE, DOUBLE, DOUBLE, DOUBLE]) as monthly_report

// 周报表可在日报基础上二次汇总,无需单独建表

// 峰谷平时段判断(与实时监控口径一致)
def isPeakTime(ts) {
    h = hour(ts)
    return (h >= 8 and h < 12) or (h >= 17 and h < 21)
}

def isValleyTime(ts) {
    h = hour(ts)
    return h >= 23 or h < 7
}

def isFlatTime(ts) {
    return not isPeakTime(ts) and not isValleyTime(ts)
}

代码解释daily_reportmonthly_report 分别存储日、月两个周期的汇总结果,是报表查询的主要数据源。isPeakTimeisValleyTimeisFlatTime 三个函数把一天划分为峰、平、谷三段,后续所有峰谷分析都依赖这组定义。这里采用与实时监控系统一致的时段规则,可以避免同一企业在不同报表中看到矛盾的峰谷电量。实际部署时,时段划分应当根据当地电价政策调整,不建议硬编码在函数里。

4.2 日报与月报生成

第二段代码实现日报与月报的生成逻辑。日报关注峰谷平电量、最大需量与负荷率;月报在日报基础上进一步汇总峰谷占比,为后续的同比环比分析提供一致口径。

python 复制代码
// 日报生成
def generateDailyReport(dt) {
    data = select * from power_stream where date(timestamp) = dt
    report = select meter_id,
                    sum(power) / 60 as total_energy,
                    sum(iif(isPeakTime(timestamp), power, 0)) / 60 as peak_energy,
                    sum(iif(isValleyTime(timestamp), power, 0)) / 60 as valley_energy,
                    sum(iif(isFlatTime(timestamp), power, 0)) / 60 as flat_energy,
                    max(power) as max_power,
                    avg(power) as avg_power,
                    avg(power) / max(power) * 100 as load_rate
             from data
             group by meter_id
    report[`date] = take(dt, report.rows())
    daily_report.append!(report)
    return report
}

// 月报生成(先按日汇总,再按月汇总)
def generateMonthlyReport(mth) {
    data = select * from power_stream where month(timestamp) = mth
    daily = select meter_id, date(timestamp) as dt,
                   sum(power) / 60 as total_energy,
                   sum(iif(isPeakTime(timestamp), power, 0)) / 60 as peak_energy,
                   sum(iif(isValleyTime(timestamp), power, 0)) / 60 as valley_energy
            from data
            group by meter_id, date(timestamp)
    report = select meter_id,
                    sum(total_energy) as total_energy,
                    avg(total_energy) as avg_daily,
                    sum(peak_energy) / sum(total_energy) * 100 as peak_ratio,
                    sum(valley_energy) / sum(total_energy) * 100 as valley_ratio
             from daily
             group by meter_id
    report[`month] = take(mth, report.rows())
    monthly_report.append!(report)
    return report
}

代码解释 :日报生成函数按 meter_id 汇总总电量、峰谷平电量、最大需量、平均功率与负荷率,其中 sum(power)/60 把秒级功率近似转换为千瓦时。月报生成函数采用"先按日汇总、再按月汇总"的两层聚合,既保证计算准确性,又降低单次聚合的数据量。需要特别注意的是,power_stream 中的功率单位应当是 kW,且采样周期为 1 秒,转换系数 60 才成立;如果采样周期不是 1 秒,需要按实际采样间隔调整分母。

4.3 同比、环比与趋势分析

第三段代码实现了同比、环比与移动平均趋势分析。同比用于观察年度变化,环比用于捕捉短期波动,移动平均则用于平滑随机噪声,凸显长期趋势。

python 复制代码
// 同比分析
def yearOverYearAnalysis(meterId, dt) {
    current = select sum(power) / 60 as energy from power_stream
              where meter_id = meterId and month(timestamp) = month(dt) and year(timestamp) = year(dt)
    lastYear = select sum(power) / 60 as energy from power_stream
               where meter_id = meterId and month(timestamp) = month(dt) and year(timestamp) = year(dt) - 1
    if (current.rows() > 0 and lastYear.rows() > 0 and lastYear.energy[0] > 0) {
        change = (current.energy[0] - lastYear.energy[0]) / lastYear.energy[0] * 100
        return dict(STRING, ANY, [["current", current.energy[0]], ["last_year", lastYear.energy[0]], ["yoy_change", change]])
    }
    return null
}
// 环比分析
def monthOverMonthAnalysis(meterId, dt) {
    current = select sum(power) / 60 as energy from power_stream
              where meter_id = meterId and timestamp < temporalAdd(dt, 1, "M") and timestamp >= temporalAdd(dt, 0, "M")
    lastMonth = select sum(power) / 60 as energy from power_stream
                where meter_id = meterId and timestamp < temporalAdd(dt, 0, "M") and timestamp >= temporalAdd(dt, -1, "M")
    if (current.rows() > 0 and lastMonth.rows() > 0 and lastMonth.energy[0] > 0) {
        change = (current.energy[0] - lastMonth.energy[0]) / lastMonth.energy[0] * 100
        return dict(STRING, ANY, [["current", current.energy[0]], ["last_month", lastMonth.energy[0]], ["mom_change", change]])
    }
    return null
}
// 趋势分析
def energyTrendAnalysis(meterId, months = 12) {
    monthly = select month(timestamp) as month, sum(power) / 60 as energy from power_stream
              where meter_id = meterId and timestamp > now() - months * 30 * 86400000
              group by month(timestamp) order by month
    monthly[`moving_avg] = mavg(monthly.energy, 3)
    return monthly
}

代码解释 :同比与环比的核心都是"同期同口径比较"。yearOverYearAnalysis 选取与当前月份相同的去年同期数据,消除了季节性影响;monthOverMonthAnalysis 使用 temporalAdd 精确计算前两个月的时间窗口,避免了跨年时 month(date - 1) 可能产生的边界问题。energyTrendAnalysis 先按自然月汇总,再用 mavg 计算 3 个月移动平均,帮助管理者识别平滑后的长期趋势。需要强调的是,同比环比的结论必须结合产量、气温、开工天数等外部因素解读,否则容易误判。例如,某月能耗同比下降 10%,如果同期产量也下降 15%,实际能效反而是恶化的。

4.4 能耗排名与强度计算

第四段代码实现了设备能耗排名、区域能耗排名,以及单位产品能耗与定额的对比。排名分析用于识别"用电大户"和"效率洼地",定额对比则用于判断是否存在超标风险。

python 复制代码
// 设备能耗排名
def deviceEnergyRanking(dt, topN = 10) {
    return select meter_id,
                  sum(power) / 60 as energy,
                  rank() over (order by sum(power) / 60 desc) as rank
           from power_stream
           where date(timestamp) = dt
           group by meter_id
           order by energy desc
           limit topN
}

// 区域能耗排名
def areaEnergyRanking(dt) {
    return select m.location,
                  sum(p.power) / 60 as energy,
                  rank() over (order by sum(p.power) / 60 desc) as rank
           from power_stream p
           left join meter_info m on p.meter_id = m.meter_id
           where date(p.timestamp) = dt
           group by m.location
           order by energy desc
}

// 单位产品能耗与定额对比
def compareWithQuota(meterId, productId, dt) {
    energy = select sum(power) / 60 as total_energy
             from power_stream
             where meter_id = meterId and date(timestamp) = dt
    output = select sum(quantity) as total_output
             from production_data
             where device_id = meterId and product_id = productId and production_date = dt
    quota = exec quota_value from energy_quota
            where device_id = meterId and product_id = productId
    if (energy.rows() == 0 or output.rows() == 0 or output.total_output[0] <= 0 or quota.size() == 0) {
        return null
    }
    actual = energy.total_energy[0] / output.total_output[0]
    deviation = (actual - quota[0]) / quota[0] * 100
    return dict(STRING, ANY, [
        ["actual", actual],
        ["quota", quota[0]],
        ["deviation", deviation],
        ["status", iif(deviation > 10, "超标", iif(deviation > 0, "接近", "达标"))]
    ])
}

代码解释deviceEnergyRankingareaEnergyRanking 使用窗口函数 rank() 对能耗进行排序,帮助快速定位高耗能设备与区域。compareWithQuota 则把能耗与产量、定额三者关联,计算单位产品实际能耗与定额的偏差。这里的关键是前置校验:如果某设备当天没有产量,能耗强度就失去意义,应当返回空值而不是强行计算。定额表 energy_quota 需要预先维护,建议由工艺或能源管理部门按产品型号、设备型号、季节等维度制定,并定期复核。

4.5 报表调度与查询接口

第五段代码把前面的函数组合成一个可运行的报表系统:创建流表、注册定时调度任务、暴露函数视图查询接口,并提供模拟数据生成任务用于本地验证。

python 复制代码
// 原始数据入口
share streamTable(100000:0,
    `meter_id`timestamp`power`,
    [SYMBOL, TIMESTAMP, DOUBLE]) as power_stream

enableTablePersistence(power_stream, true, true, 10000000)

// 报表生成调度任务
def reportTask() {
    while (true) {
        t = now()
        if (hour(t) == 0 and minute(t) < 5) {
            yesterday = date(t - 86400000)
            generateDailyReport(yesterday)
            print("生成日报: " + yesterday)
        }
        if (day(t) == 1 and hour(t) == 0 and minute(t) < 5) {
            lastMonth = month(t - 86400000)
            generateMonthlyReport(lastMonth)
            print("生成月报: " + lastMonth)
        }
        sleep(300000)
    }
}

submitJob("report_task", "报表生成调度", reportTask)

// 函数视图查询接口
def getDailyReport(meterId, startDate, endDate) {
    return select * from daily_report
           where meter_id = meterId and date between startDate and endDate
           order by date
}

def getMonthlyReport(meterId, months = 12) {
    return select * from monthly_report
           where meter_id = meterId
           order by month desc
           limit months
}

def getEnergyComparison(meterId, dt) {
    return dict(STRING, ANY, [
        ["yoy", yearOverYearAnalysis(meterId, dt)],
        ["mom", monthOverMonthAnalysis(meterId, dt)]
    ])
}

addFunctionView(getDailyReport)
addFunctionView(getMonthlyReport)
addFunctionView(getEnergyComparison)

代码解释reportTask 每 5 分钟检查一次时间,在每日 0 点生成日报、每月 1 日 0 点生成月报。这种基于轮询的调度简单可靠,适合报表生成这种对触发精度要求不高的任务;如果需要更高精度,可以结合外部调度系统(如 Airflow、Quartz)通过 API 触发。getDailyReportgetMonthlyReportgetEnergyComparison 三个函数视图分别支撑日报查询、月报查询和同比环比查询,前端可以直接调用。本地验证时,可以参考 DolphinDB 实时监控系统中的模拟数据任务向 power_stream 写入测试数据;真实上线时则由采集网关替换。


五、可视化与报表呈现

5.1 报表可视化设计

报表的可视化目标与实时监控大屏不同:监控大屏强调"当前状态",报表强调"历史对比与决策支持"。因此,报表页面应当以表格为主、图表为辅,突出数字本身的可读性,同时用图表展示趋势与结构。
#mermaid-svg-W2c38h9eZhOVHMCk{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-W2c38h9eZhOVHMCk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W2c38h9eZhOVHMCk .error-icon{fill:#552222;}#mermaid-svg-W2c38h9eZhOVHMCk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W2c38h9eZhOVHMCk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W2c38h9eZhOVHMCk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W2c38h9eZhOVHMCk .marker.cross{stroke:#333333;}#mermaid-svg-W2c38h9eZhOVHMCk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W2c38h9eZhOVHMCk p{margin:0;}#mermaid-svg-W2c38h9eZhOVHMCk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W2c38h9eZhOVHMCk .cluster-label text{fill:#333;}#mermaid-svg-W2c38h9eZhOVHMCk .cluster-label span{color:#333;}#mermaid-svg-W2c38h9eZhOVHMCk .cluster-label span p{background-color:transparent;}#mermaid-svg-W2c38h9eZhOVHMCk .label text,#mermaid-svg-W2c38h9eZhOVHMCk span{fill:#333;color:#333;}#mermaid-svg-W2c38h9eZhOVHMCk .node rect,#mermaid-svg-W2c38h9eZhOVHMCk .node circle,#mermaid-svg-W2c38h9eZhOVHMCk .node ellipse,#mermaid-svg-W2c38h9eZhOVHMCk .node polygon,#mermaid-svg-W2c38h9eZhOVHMCk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W2c38h9eZhOVHMCk .rough-node .label text,#mermaid-svg-W2c38h9eZhOVHMCk .node .label text,#mermaid-svg-W2c38h9eZhOVHMCk .image-shape .label,#mermaid-svg-W2c38h9eZhOVHMCk .icon-shape .label{text-anchor:middle;}#mermaid-svg-W2c38h9eZhOVHMCk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W2c38h9eZhOVHMCk .rough-node .label,#mermaid-svg-W2c38h9eZhOVHMCk .node .label,#mermaid-svg-W2c38h9eZhOVHMCk .image-shape .label,#mermaid-svg-W2c38h9eZhOVHMCk .icon-shape .label{text-align:center;}#mermaid-svg-W2c38h9eZhOVHMCk .node.clickable{cursor:pointer;}#mermaid-svg-W2c38h9eZhOVHMCk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W2c38h9eZhOVHMCk .arrowheadPath{fill:#333333;}#mermaid-svg-W2c38h9eZhOVHMCk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W2c38h9eZhOVHMCk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W2c38h9eZhOVHMCk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W2c38h9eZhOVHMCk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W2c38h9eZhOVHMCk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W2c38h9eZhOVHMCk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W2c38h9eZhOVHMCk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W2c38h9eZhOVHMCk .cluster text{fill:#333;}#mermaid-svg-W2c38h9eZhOVHMCk .cluster span{color:#333;}#mermaid-svg-W2c38h9eZhOVHMCk 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-W2c38h9eZhOVHMCk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W2c38h9eZhOVHMCk rect.text{fill:none;stroke-width:0;}#mermaid-svg-W2c38h9eZhOVHMCk .icon-shape,#mermaid-svg-W2c38h9eZhOVHMCk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W2c38h9eZhOVHMCk .icon-shape p,#mermaid-svg-W2c38h9eZhOVHMCk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W2c38h9eZhOVHMCk .icon-shape .label rect,#mermaid-svg-W2c38h9eZhOVHMCk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W2c38h9eZhOVHMCk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W2c38h9eZhOVHMCk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W2c38h9eZhOVHMCk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 报表首页
日报表
周报表
月报表
同比环比分析
能耗排名
定额对比
表格明细
峰谷平堆叠图
同比曲线
环比柱状图
Top N 设备排名
超标红色标记

5.2 图表选型建议

不同类型的分析适合不同的图表。日报适合用表格 + 堆叠柱状图展示峰谷平结构;同比环比适合用组合图(柱状图表示当期,折线表示增长率);能耗排名适合用横向条形图;定额对比适合用仪表盘或红绿灯标记。

5.3 报表导出与分享

工业报表通常需要导出为 Excel 或 PDF,以便通过邮件分发给管理层或在会议中展示。前端可以调用函数视图获取 JSON 数据,再使用 SheetJS、pdfmake 等库生成文件。对于周期性发送的固定报表,建议在后端增加一个邮件发送任务,与 DolphinDB 的报表调度任务联动。导出时应注意数据安全:包含设备明细的报表应当按收件人权限过滤,避免把敏感生产数据发送给无关人员。

5.4 报表权限与数据隔离

能耗报表往往涉及企业生产数据,需要按角色控制可见范围。建议设计三级权限:公司级报表对高管开放,展示全厂汇总与趋势;部门级报表对车间主任开放,展示本车间设备排名与定额达成;班组级报表对现场运维开放,展示当班能耗明细。DolphinDB 可以通过为不同角色创建不同的函数视图来实现数据隔离:高管视图查询全量汇总表,车间视图增加 where location = '车间A' 过滤条件,班组视图进一步限定到当班时间窗口。这样即使前端使用同一套代码,后端也能保证数据不越权,同时简化前端开发。


六、边界条件与实施风险

6.1 数据口径一致性风险

同比环比最容易出现的问题就是口径不一致。例如,今年 2 月有 28 天,去年 2 月有 29 天;某月设备检修导致开工天数减少;产品 mix 变化导致平均能耗波动。这些都会影响同比环比结论。建议在报表中增加"产量""开工天数""平均气温"等辅助指标,帮助读者正确解读能耗变化。

6.2 缺失数据与异常值处理

电表通信中断、系统升级、传感器故障都会导致数据缺失。报表生成时不能简单地把缺失当作零值,否则累计能耗会偏低。建议对缺失时段做标记,并在报表中展示"数据完整率"。对于明显异常值(如功率突增到额定值的数倍),可以在计算前用 3-sigma 或箱线图方法剔除。

6.3 定额制定的科学性

定额如果定得过松,会失去约束作用;定得过严,会导致频繁误报,基层人员产生抵触。建议采用"历史基线 + 行业标杆 + 节能目标"三者结合的方式制定定额,并设置 3~6 个月的试运行期,根据实际达成率逐步收紧。

6.4 计算性能边界

当历史数据量达到 TB 级时,全表扫描的同比环比查询可能变慢。此时应当采用分层存储:原始秒级数据按日期分区,日报/月报等汇总结果存入独立的结果表。函数视图应当优先查询汇总结果表,只在必要时才回查原始数据。

6.5 与碳排放核算的衔接

能耗报表不仅服务于节能管理,也是碳排放核算的数据源头。电、气、汽、水等能源类型乘以对应的排放因子,即可得到二氧化碳当量。建议在月报中增加 co2_emission 字段,把能耗统计与碳核算打通。这样,同一份报表既可以回答"用了多少电",也可以回答"产生了多少碳排放",为企业的双碳目标提供统一的数据基础。排放因子可以参照国家生态环境部发布的最新系数,并允许按地区电网结构进行微调。


七、总结

本文围绕 DolphinDB 能耗统计分析,从报表体系设计、日报/周报/月报生成、同比环比分析、能耗排名、能耗强度与定额对比,到可视化呈现与实施风险,系统梳理了从原始数据到管理报表的完整链路。核心要点有三:第一,能耗统计分析是节能管理的基础,只有把原始数据转化为结构化报表,才能支撑决策;第二,同比环比分析必须注意口径一致,否则容易得出错误结论;第三,能耗强度与定额对比是评估能效的关键,但定额需要动态维护,不能一成不变。

在实际落地中,建议采用分阶段推进策略。第一阶段先跑通日报与月报,确保数据完整与指标口径正确;第二阶段引入同比环比与设备排名,识别高耗能点;第三阶段建立能耗强度基线与定额体系,形成闭环管理。DolphinDB 3.x 的时序聚合与函数视图能力可以显著降低这一过程的开发与运维成本,而报表设计的核心思想与方法则属于经典管理工程范畴,长期适用。未来还可以把能耗报表与碳排放因子结合,自动生成碳排放报表,助力企业实现绿色制造与双碳目标。

思考题

  1. 如何在 DolphinDB 中处理闰年、节假日等异常周期对同比环比的影响?
  2. 当设备产量数据缺失时,能耗强度应当如何标记与呈现?
  3. 如何利用 DolphinDB 的分区机制优化跨年历史数据的报表查询性能?

参考链接

相关推荐
云端漫步19871 小时前
HarmonyOS NEXT AI 智能生活助手:AI 待办事项生成
人工智能·华为·生活·harmonyos
武子康1 小时前
开放权重不是唯一控制权:Kimi K3、Tencent Hy3 与 ByteDance Seed 的九项交付比较
人工智能·llm·agent
Zzj_tju1 小时前
Instruction Tuning 论文精读路线:从 Supervised Fine-Tuning 到 Instruction Following
人工智能·笔记·学习·语言模型·自然语言处理
2601_960906721 小时前
AI研发加速中式及泛亚洲
人工智能·vscode·macos·sublime text·phpstorm
为啥全要学1 小时前
在大语言模型上使用 PPO 算法
人工智能·算法·语言模型
studyrunner1 小时前
【AI开源】Buzz 实战教程:搭建人类与多 AI Agent 协同工作的自托管工作区
人工智能·开源
动物园猫1 小时前
夜间野生动物目标检测数据集:17类别、17,000张图像 | 目标检测
人工智能·目标检测·计算机视觉
小马9261 小时前
2026年8月4日科技热点深度解析:AI大模型群雄逐鹿、卫星互联网组网提速、半导体封装材料革命
人工智能·科技·deepseek