时序数据仓库:电力负荷分析与预测

将 PJM 电网小时级负荷数据构建为多层时序数仓,输出峰谷定价策略、负荷曲线分析和异常检测告警。本文以真实 PJM Interconnection 电力数据集(2018 年,4 天 96 条小时级记录)为基础,端到端演示 Kafka PIPE → Bronze → Silver → Gold 的完整构建过程,覆盖 Window Function(LAG / LEAD / ROWS BETWEEN)、峰谷识别、同期对比和 Z-score 异常检测四项时序核心能力。


概述

电力负荷数据仓库的典型链路是:智能电表上报 → 实时接入 → 原始存储(Bronze)→ 小时聚合与清洗(Silver)→ 日级峰谷指标(Gold)→ 负荷预测与 BI

云器 Lakehouse 通过以下组合解决几个核心问题:

问题解决方案
智能电表分钟级数据实时接入,数据量大Kafka PIPE 持续摄取,无需手写消费者
小时聚合、峰谷标注需随上游数据自动更新Dynamic Table,声明式 SQL,系统自动增量计算
负荷曲线分析需要前后小时对比
LAG
LAG
/
LEAD
LEAD
窗口函数,跨行引用无需自 JOIN
滑动均值平滑噪声数据
ROWS BETWEEN N PRECEDING AND CURRENT ROW
ROWS BETWEEN N PRECEDING AND CURRENT ROW
同季节、冬夏对比分析条件聚合 +
CASE WHEN MONTH()
CASE WHEN MONTH()
日级峰谷价差、峰谷比统计Gold 层聚合,支持任意粒度下钻

涉及的 SQL 命令

命令 / 函数用途说明
CREATE TABLE
CREATE TABLE
建 Bronze 层原始负荷表和电表元数据表静态表,作为 Dynamic Table 上游
CREATE BLOOMFILTER INDEX
CREATE BLOOMFILTER INDEX
event_time
event_time
列创建索引
加速按时间范围的点查过滤
CREATE PIPE
CREATE PIPE
创建 Kafka 持续摄取管道绑定到 Bronze 层目标表
CREATE DYNAMIC TABLE
CREATE DYNAMIC TABLE
创建 Silver / Gold 层增量计算表系统自动识别上游变更并增量刷新
LAG
LAG
/
LEAD
LEAD
引用前后行的负荷值计算小时级变化量和趋势
AVG ... OVER (ROWS BETWEEN)
AVG ... OVER (ROWS BETWEEN)
滑动窗口均值平滑噪声、识别趋势
STDDEV
STDDEV
计算日内负荷标准差Z-score 异常检测基础
REFRESH DYNAMIC TABLE
REFRESH DYNAMIC TABLE
手动触发一次刷新初次构建或调试时使用

前置准备

本文所有示例在

best_practice_energy_ts
best_practice_energy_ts
Schema 下运行。

CREATE SCHEMA IF NOT EXISTS best_practice_energy_ts;


Bronze 层:原始负荷数据表

建表

doc_pjme_load_raw
doc_pjme_load_raw
存储 PJM 东部电网(PJME)的小时级负荷数据,每行代表一个小时观测点。

CREATE TABLE IF NOT EXISTS best_practice_energy_ts.doc_pjme_load_raw ( event_time TIMESTAMP, load_mw DOUBLE );

同时建立电表区域主数据表,供后续维度关联使用:

CREATE TABLE IF NOT EXISTS best_practice_energy_ts.doc_meter_metadata ( meter_id STRING, region STRING, voltage_level STRING, capacity_mw DOUBLE, install_year INT, operator STRING );

创建 Bloomfilter Index

时序查询几乎都带

event_time
event_time
的时间范围过滤,该列适合 Bloomfilter Index 加速等值和范围过滤。

CREATE BLOOMFILTER INDEX IF NOT EXISTS idx_bf_event_time ON TABLE doc_pjme_load_raw (event_time);

配置 Kafka PIPE

智能电表上报数据通过 Kafka topic 实时流入。生产环境替换 broker 地址和 topic 名称后即可使用。

方式一:通过 Kafka 实际写入(推荐)

先建原始字符串接收表,再创建 PIPE:

CREATE TABLE IF NOT EXISTS best_practice_energy_ts.doc_kafka_raw_load ( kafka_value STRING ); CREATE PIPE IF NOT EXISTS best_practice_energy_ts.pipe_energy_load VIRTUAL_CLUSTER = 'DEFAULT' BATCH_INTERVAL_IN_SECONDS = '60' AS COPY INTO best_practice_energy_ts.doc_kafka_raw_load FROM ( SELECT CAST(value AS STRING) AS kafka_value FROM READ_KAFKA( '<kafka-broker>:9092', -- 替换为实际 broker 地址 'energy.load.realtime', -- topic 名称 '', 'cz_energy_consumer', -- consumer group ID '','','','', 'raw', 'raw', 0, map() ) );

触发 Kafka 写入的 Python 生产者示例(使用

kafka-python
kafka-python
):

from kafka import KafkaProducer import json, time, random producer = KafkaProducer( bootstrap_servers=['<kafka-broker>:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8') ) # 模拟智能电表每分钟上报一次 while True: record = { "event_time": time.strftime('%Y-%m-%d %H:%M:%S'), "meter_id": "PJME", "load_mw": round(random.uniform(25000, 55000), 1) } producer.send('energy.load.realtime', value=record) print(f"Sent: {record}") time.sleep(60)

方式二:INSERT 模拟(无 Kafka 环境时)

若暂未配置 Kafka,可通过

INSERT INTO
INSERT INTO
直接写入目标表,模拟消息已解析写入的效果,便于验证后续 Dynamic Table 逻辑。

本文使用 PJM Hourly Energy Consumption 数据集(CC0 协议),选取 2018 年冬季(1 月)和夏季(7 月)典型日各两天,共 96 条小时级记录:

从本地 CSV 导入数据(推荐):

-- 第一步:通过 SQL PUT 将本地 CSV 文件上传到 User Volume PUT '/path/to/your/doc_pjme_load_raw.csv' TO USER VOLUME FILE 'doc_pjme_load_raw.csv';

-- 第二步:从 User Volume COPY INTO 表 COPY INTO best_practice_energy_ts.doc_pjme_load_raw FROM USER VOLUME USING csv OPTIONS('header'='true', 'sep'=',', 'nullValue'='') FILES ('doc_pjme_load_raw.csv');

也可直接内联插入小批量测试数据(不需要 CSV 文件):

INSERT INTO best_practice_energy_ts.doc_pjme_load_raw (event_time, load_mw) VALUES (CAST('2018-01-01 00:00:00' AS TIMESTAMP), 39928.0), (CAST('2018-01-01 01:00:00' AS TIMESTAMP), 38925.0), -- ... 共 96 条,覆盖 2018-01-01, 2018-01-15, 2018-07-01, 2018-07-15 (CAST('2018-07-15 23:00:00' AS TIMESTAMP), 37301.0);

验证数据加载结果:

SELECT COUNT(*) AS total_rows FROM best_practice_energy_ts.doc_pjme_load_raw;

total_rows ---------- 96

写入电表元数据:

从本地 CSV 导入数据(推荐):

-- 第一步:通过 SQL PUT 将本地 CSV 文件上传到 User Volume PUT '/path/to/your/doc_meter_metadata.csv' TO USER VOLUME FILE 'doc_meter_metadata.csv';

-- 第二步:从 User Volume COPY INTO 表 COPY INTO best_practice_energy_ts.doc_meter_metadata FROM USER VOLUME USING csv OPTIONS('header'='true', 'sep'=',', 'nullValue'='') FILES ('doc_meter_metadata.csv');

也可直接内联插入小批量测试数据(不需要 CSV 文件):

INSERT INTO best_practice_energy_ts.doc_meter_metadata VALUES ('PJME', 'East PJM', '345kV', 60000.0, 1997, 'PJM Interconnection'), ('AEP', 'American Electric Power', '138kV', 22000.0, 2004, 'AEP Ohio'), ('DAYTON', 'Dayton Power', '69kV', 6500.0, 2003, 'AES Ohio'), ('COMED', 'ComEd Chicago', '345kV', 25000.0, 2002, 'Commonwealth Edison'), ('DEOK', 'Duke Energy Ohio-KY', '138kV', 8000.0, 2004, 'Duke Energy');


Silver 层 Dynamic Table:小时聚合与峰谷标注

Silver 层以小时为粒度聚合 Bronze 原始数据,计算均值、峰值、谷值,并按电价时段标注

tariff_period
tariff_period
(峰时/谷时)。

峰谷时段划分(示例,可按业务实际调整):

  • 峰时(peak):每天 09:00–21:59
  • 谷时(valley):00:00–08:59、22:00–23:59

CREATE DYNAMIC TABLE IF NOT EXISTS best_practice_energy_ts.doc_silver_hourly_load AS SELECT DATE_TRUNC('hour', event_time) AS hour_ts, DATE(event_time) AS load_date, HOUR(event_time) AS load_hour, ROUND(AVG(load_mw), 1) AS avg_load_mw, ROUND(MAX(load_mw), 1) AS max_load_mw, ROUND(MIN(load_mw), 1) AS min_load_mw, COUNT(*) AS data_points, CASE WHEN HOUR(event_time) BETWEEN 9 AND 21 THEN 'peak' ELSE 'valley' END AS tariff_period FROM best_practice_energy_ts.doc_pjme_load_raw WHERE load_mw IS NOT NULL AND load_mw > 0 GROUP BY DATE_TRUNC('hour', event_time), DATE(event_time), HOUR(event_time);

手动触发首次刷新:

REFRESH DYNAMIC TABLE best_practice_energy_ts.doc_silver_hourly_load; SELECT COUNT(*) AS silver_count FROM best_practice_energy_ts.doc_silver_hourly_load;

silver_count ------------ 96

配置 Silver 层刷新调度任务

通过 cz-cli 在 Studio 的

energy_ts
energy_ts
文件夹下创建刷新任务:

# 1. 创建文件夹 cz-cli task create-folder energy_ts -p skill_test # 2. 创建 SQL 任务 cz-cli task create refresh_silver_hourly_load --type SQL --folder energy_ts -p skill_test # 3. 设置任务内容 cz-cli task save-content refresh_silver_hourly_load \ --content "REFRESH DYNAMIC TABLE best_practice_energy_ts.doc_silver_hourly_load;" \ -p skill_test # 4. 设置调度:每 30 分钟刷新一次 cz-cli task save-cron refresh_silver_hourly_load \ --cron "0 */30 * * * ? *" -p skill_test

任务创建后,可在 Studio 任务界面为

refresh_silver_hourly_load
refresh_silver_hourly_load
附加数据质量检查规则(如
COUNT(*) > 0
COUNT(*) > 0
)和告警通知,无需修改 DT 定义本身。


时序分析:窗口函数实战

以下分析均基于 Silver 层数据,展示三类典型时序计算。

逐小时负荷变化(LAG / LEAD)

LAG
LAG
引用前一小时数据,
LEAD
LEAD
引用后一小时数据,计算每小时的负荷变化量:

SELECT load_date, load_hour, avg_load_mw, LAG(avg_load_mw, 1) OVER (PARTITION BY load_date ORDER BY load_hour) AS prev_hour_mw, LEAD(avg_load_mw, 1) OVER (PARTITION BY load_date ORDER BY load_hour) AS next_hour_mw, ROUND( avg_load_mw - LAG(avg_load_mw, 1) OVER (PARTITION BY load_date ORDER BY load_hour), 1 ) AS hour_delta_mw FROM best_practice_energy_ts.doc_silver_hourly_load WHERE load_date = CAST('2018-07-01' AS DATE) ORDER BY load_hour;

前 8 行结果(节选):

load_date | load_hour | avg_load_mw | prev_hour_mw | next_hour_mw | hour_delta_mw -----------+-----------+-------------+--------------+--------------+-------------- 2018-07-01 | 0 | 37751 | null | 34716 | null 2018-07-01 | 1 | 34716 | 37751 | 32345 | -3035 2018-07-01 | 2 | 32345 | 34716 | 30546 | -2371 2018-07-01 | 3 | 30546 | 32345 | 29300 | -1799 2018-07-01 | 4 | 29300 | 30546 | 28511 | -1246 2018-07-01 | 5 | 28511 | 29300 | 27992 | -789 2018-07-01 | 6 | 27992 | 28511 | 28211 | -519 2018-07-01 | 7 | 28211 | 27992 | 30337 | 219

结果解读:凌晨 1 时到 7 时负荷持续下降(

hour_delta_mw
hour_delta_mw
为负值),从 37,751 MW 降至 27,992 MW,降幅达 26%,符合夏季夜间空调降温需求减少的规律。7 时开始转正,标志着早峰开始。

3 小时滑动均值(ROWS BETWEEN)

ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
计算过去 3 小时的滑动均值,平滑短期波动:

SELECT load_date, load_hour, avg_load_mw, ROUND(AVG(avg_load_mw) OVER ( PARTITION BY load_date ORDER BY load_hour ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ), 1) AS rolling_3h_avg_mw FROM best_practice_energy_ts.doc_silver_hourly_load WHERE load_date = CAST('2018-07-01' AS DATE) ORDER BY load_hour;

前 10 行结果(节选):

load_date | load_hour | avg_load_mw | rolling_3h_avg_mw -----------+-----------+-------------+------------------ 2018-07-01 | 0 | 37751 | 37751 2018-07-01 | 1 | 34716 | 36233.5 2018-07-01 | 2 | 32345 | 34937.3 2018-07-01 | 3 | 30546 | 32535.7 2018-07-01 | 4 | 29300 | 30730.3 2018-07-01 | 5 | 28511 | 29452.3 2018-07-01 | 6 | 27992 | 28601 2018-07-01 | 7 | 28211 | 28238 2018-07-01 | 8 | 30337 | 28846.7 2018-07-01 | 9 | 33759 | 30769

结果解读:滑动均值(

rolling_3h_avg_mw
rolling_3h_avg_mw
)相比瞬时值(
avg_load_mw
avg_load_mw
)变化更平缓,特别是在早晨(8–9 时)负荷骤升时,滑动均值起到延迟效果,可用于过滤仪表波动引起的噪声。

日内峰谷识别与峰值占比

计算当日峰值、谷值及每个小时占峰值的百分比:

SELECT load_date, load_hour, avg_load_mw, tariff_period, MAX(avg_load_mw) OVER (PARTITION BY load_date) AS daily_peak_mw, MIN(avg_load_mw) OVER (PARTITION BY load_date) AS daily_valley_mw, ROUND(100.0 * avg_load_mw / MAX(avg_load_mw) OVER (PARTITION BY load_date), 1) AS pct_of_peak FROM best_practice_energy_ts.doc_silver_hourly_load WHERE load_date = CAST('2018-07-01' AS DATE) ORDER BY load_hour;

前 10 行结果(节选):

load_date | load_hour | avg_load_mw | tariff_period | daily_peak_mw | daily_valley_mw | pct_of_peak -----------+-----------+-------------+---------------+---------------+-----------------+------------ 2018-07-01 | 0 | 37751 | valley | 51803 | 27992 | 72.9 2018-07-01 | 1 | 34716 | valley | 51803 | 27992 | 67 2018-07-01 | 2 | 32345 | valley | 51803 | 27992 | 62.4 2018-07-01 | 3 | 30546 | valley | 51803 | 27992 | 59 2018-07-01 | 4 | 29300 | valley | 51803 | 27992 | 56.6 2018-07-01 | 5 | 28511 | valley | 51803 | 27992 | 55 2018-07-01 | 6 | 27992 | valley | 51803 | 27992 | 54 2018-07-01 | 7 | 28211 | valley | 51803 | 27992 | 54.5 2018-07-01 | 8 | 30337 | valley | 51803 | 27992 | 58.6 2018-07-01 | 9 | 33759 | peak | 51803 | 27992 | 65.2

结果解读:2018-07-01 日内峰谷差达 23,811 MW(峰谷比 46%,见 Gold 层),远高于冬季(约 16%)。凌晨 6 时(27,992 MW)为全天谷底,仅为峰值的 54%,是储能充电和谷电定价的最优时段。


Gold 层 Dynamic Table:日级峰谷指标

Gold 层以日为粒度,从 Silver 层聚合输出峰谷价差、峰谷比和分时段均值,供 BI 看板和定价系统消费。

CREATE DYNAMIC TABLE IF NOT EXISTS best_practice_energy_ts.doc_gold_daily_load_profile AS SELECT load_date, COUNT(*) AS hours_recorded, ROUND(AVG(avg_load_mw), 1) AS daily_avg_mw, ROUND(MAX(max_load_mw), 1) AS daily_peak_mw, ROUND(MIN(min_load_mw), 1) AS daily_valley_mw, ROUND(MAX(max_load_mw) - MIN(min_load_mw), 1) AS peak_valley_spread_mw, ROUND( 100.0 * (MAX(max_load_mw) - MIN(min_load_mw)) / MAX(max_load_mw), 1 ) AS peak_valley_ratio_pct, ROUND( SUM(CASE WHEN tariff_period = 'peak' THEN avg_load_mw ELSE 0 END) / NULLIF(SUM(CASE WHEN tariff_period = 'peak' THEN 1 ELSE 0 END), 0), 1 ) AS peak_period_avg_mw, ROUND( SUM(CASE WHEN tariff_period = 'valley' THEN avg_load_mw ELSE 0 END) / NULLIF(SUM(CASE WHEN tariff_period = 'valley' THEN 1 ELSE 0 END), 0), 1 ) AS valley_period_avg_mw, CASE WHEN MONTH(load_date) IN (6,7,8) THEN 'summer' WHEN MONTH(load_date) IN (12,1,2) THEN 'winter' WHEN MONTH(load_date) IN (3,4,5) THEN 'spring' ELSE 'autumn' END AS season FROM best_practice_energy_ts.doc_silver_hourly_load GROUP BY load_date;

手动触发刷新并查看结果:

REFRESH DYNAMIC TABLE best_practice_energy_ts.doc_gold_daily_load_profile; SELECT load_date, daily_avg_mw, daily_peak_mw, daily_valley_mw, peak_valley_spread_mw, peak_valley_ratio_pct, peak_period_avg_mw, valley_period_avg_mw, season FROM best_practice_energy_ts.doc_gold_daily_load_profile ORDER BY load_date;

load_date | daily_avg_mw | daily_peak_mw | daily_valley_mw | peak_valley_spread_mw | peak_valley_ratio_pct | peak_period_avg_mw | valley_period_avg_mw | season -----------+--------------+---------------+-----------------+-----------------------+-----------------------+--------------------+---------------------+------- 2018-01-01 | 40191 | 44343 | 37742 | 6601 | 14.9 | 40964 | 39277.5 | winter 2018-01-15 | 39257.8 | 42249 | 35242 | 7007 | 16.6 | 40938.5 | 37271.5 | winter 2018-07-01 | 40584.8 | 51803 | 27992 | 23811 | 46 | 46463 | 33637.9 | summer 2018-07-15 | 34204.1 | 42348 | 26712 | 15636 | 36.9 | 37203.7 | 30659.1 | summer

结果解读

  • 冬夏对比:冬季(1 月)峰谷差约 6,601–7,007 MW,峰谷比 15–17%;夏季(7 月)峰谷差高达 15,636–23,811 MW,峰谷比 37–46%。夏季用电行为极度集中在午后和傍晚(空调负荷),是峰谷定价的最大价值区间。
  • 2018-07-01 极端日:峰值 51,803 MW 为数据集中最高点,峰谷比 46%;而 2018-07-15 只有 42,348 MW,说明同月不同日的负荷变化显著,不宜使用月平均简单估算日峰值。
  • 峰谷时段均值差:夏季 2018-07-01 峰时均值(46,463 MW)是谷时(33,638 MW)的 1.38 倍,说明分时电价在夏季的调峰激励效果最强。

配置 Gold 层刷新调度任务

# 创建每日 01:00 刷新任务(依赖 Silver 层数据已更新) cz-cli task create refresh_gold_daily_load_profile --type SQL --folder energy_ts -p skill_test cz-cli task save-content refresh_gold_daily_load_profile \ --content "REFRESH DYNAMIC TABLE best_practice_energy_ts.doc_gold_daily_load_profile;" \ -p skill_test # 每天凌晨 01:00 刷新 cz-cli task save-cron refresh_gold_daily_load_profile \ --cron "0 0 1 * * ? *" -p skill_test


冬夏季节同期对比

将 Silver 层数据按月份分组,对每个小时点进行冬夏季平均负荷对比:

SELECT load_hour, ROUND(AVG(CASE WHEN MONTH(load_date) = 7 THEN avg_load_mw END), 1) AS summer_avg_mw, ROUND(AVG(CASE WHEN MONTH(load_date) = 1 THEN avg_load_mw END), 1) AS winter_avg_mw, ROUND( AVG(CASE WHEN MONTH(load_date) = 7 THEN avg_load_mw END) - AVG(CASE WHEN MONTH(load_date) = 1 THEN avg_load_mw END), 1 ) AS summer_vs_winter_delta FROM best_practice_energy_ts.doc_silver_hourly_load GROUP BY load_hour ORDER BY load_hour;

前 12 小时结果(节选):

load_hour | summer_avg_mw | winter_avg_mw | summer_vs_winter_delta ----------+---------------+---------------+----------------------- 0 | 36147 | 38300 | -2153 1 | 33269 | 37325 | -4056 2 | 31082.5 | 36793 | -5710.5 3 | 29477.5 | 36525 | -7047.5 4 | 28386 | 36603 | -8217 5 | 27671.5 | 37167.5 | -9496 6 | 27352 | 38453.5 | -11101.5 7 | 27480 | 40041.5 | -12561.5 8 | 29054 | 40980 | -11926 9 | 31691.5 | 41242 | -9550.5 10 | 34662 | 40961.5 | -6299.5 11 | 37456 | 40531.5 | -3075.5

结果解读:冬季凌晨至上午(0–11 时)负荷普遍高于夏季,在早晨 7 时差值达 -12,561 MW(冬季比夏季高出约 46%),根本原因是冬季早高峰取暖需求叠加日出晚导致的早高峰前移。中午之后夏季逐渐反超,午后空调负荷主导。这一规律对按季节动态调整峰谷时段划分有重要指导意义。


异常检测:Z-score 方法

基于日内标准差识别当天的异常时段,Z-score 绝对值 > 2.0 标记为异常:

WITH stats AS ( SELECT load_date, AVG(avg_load_mw) AS mean_mw, STDDEV(avg_load_mw) AS std_mw FROM best_practice_energy_ts.doc_silver_hourly_load GROUP BY load_date ) SELECT h.load_date, h.load_hour, h.avg_load_mw, ROUND((h.avg_load_mw - s.mean_mw) / NULLIF(s.std_mw, 0), 2) AS z_score, CASE WHEN ABS((h.avg_load_mw - s.mean_mw) / NULLIF(s.std_mw, 0)) > 2.0 THEN 'anomaly' ELSE 'normal' END AS anomaly_flag FROM best_practice_energy_ts.doc_silver_hourly_load h JOIN stats s ON h.load_date = s.load_date WHERE h.load_date = CAST('2018-07-01' AS DATE) ORDER BY h.load_hour;

前 10 行结果(节选):

load_date | load_hour | avg_load_mw | z_score | anomaly_flag -----------+-----------+-------------+---------+------------- 2018-07-01 | 0 | 37751 | -0.32 | normal 2018-07-01 | 1 | 34716 | -0.66 | normal 2018-07-01 | 2 | 32345 | -0.93 | normal 2018-07-01 | 3 | 30546 | -1.14 | normal 2018-07-01 | 4 | 29300 | -1.28 | normal 2018-07-01 | 5 | 28511 | -1.37 | normal 2018-07-01 | 6 | 27992 | -1.43 | normal 2018-07-01 | 7 | 28211 | -1.4 | normal 2018-07-01 | 8 | 30337 | -1.16 | normal 2018-07-01 | 9 | 33759 | -0.77 | normal

日内标准差统计:

SELECT load_date, ROUND(AVG(avg_load_mw), 1) AS mean_mw, ROUND(STDDEV(avg_load_mw), 1) AS stddev_mw, ROUND(AVG(avg_load_mw) + 2 * STDDEV(avg_load_mw), 1) AS upper_2sigma, ROUND(AVG(avg_load_mw) - 2 * STDDEV(avg_load_mw), 1) AS lower_2sigma FROM best_practice_energy_ts.doc_silver_hourly_load GROUP BY load_date ORDER BY load_date;

load_date | mean_mw | stddev_mw | upper_2sigma | lower_2sigma -----------+----------+-----------+--------------+------------- 2018-01-01 | 40191 | 2081.4 | 44353.9 | 36028.2 2018-01-15 | 39257.8 | 2500.5 | 44258.8 | 34256.7 2018-07-01 | 40584.8 | 8833.6 | 58252.1 | 22917.6 2018-07-15 | 34204.1 | 5398.7 | 45001.4 | 23406.8

结果解读:冬季(1 月)日内标准差约 2,000–2,500 MW,负荷曲线平缓;夏季(7 月)标准差高达 5,000–8,800 MW,日内波动极大。夏季 2018-07-01 的 2σ 上界为 58,252 MW,而实际峰值 51,803 MW 未超出该界,因此没有 Z-score 异常——说明本日高峰虽绝对值大,但在该日内的统计规律范围内;若出现设备故障导致瞬时跳跃,才会触发 Z-score > 2 的异常标记。


负荷预测(External Function 集成)

Gold 层数据可对接时序预测模型。以下展示通过 External Function 调用外部 Prophet / ARIMA 预测服务的架构方式(代码示意,实际部署需配置 API Connection):

-- 创建调用 Prophet 预测服务的 External Function(示意) -- CREATE EXTERNAL FUNCTION best_practice_energy_ts.predict_next_24h( -- history_load ARRAY<DOUBLE>, -- history_timestamps ARRAY<STRING> -- ) -- RETURNS STRUCT<forecast_mw ARRAY<DOUBLE>, forecast_timestamps ARRAY<STRING>> -- LANGUAGE PYTHON -- HANDLER = 'ProphetForecast.predict' -- RESOURCES = 'volume://functions/prophet_forecast.zip' -- CONNECTION = my_api_connection; -- 使用预测函数(示意,配置 External Function 后可直接调用) SELECT load_date, daily_avg_mw, daily_peak_mw -- predict_next_24h(...) AS forecast_result -- 实际调用时取消注释 FROM best_practice_energy_ts.doc_gold_daily_load_profile ORDER BY load_date;


数仓对象总览

全部构建完成后,

best_practice_energy_ts
best_practice_energy_ts
Schema 下的对象:

SHOW TABLES IN best_practice_energy_ts;

schema_name | table_name | is_dynamic -------------------------+------------------------------+----------- best_practice_energy_ts | doc_gold_daily_load_profile | true best_practice_energy_ts | doc_kafka_raw_load | false best_practice_energy_ts | doc_meter_metadata | false best_practice_energy_ts | doc_pjme_load_raw | false best_practice_energy_ts | doc_silver_hourly_load | true

架构结构:

Smart Meter(实时) Historical CSV(批量) │ │ ▼ pipe_energy_load ▼ INSERT doc_kafka_raw_load doc_pjme_load_raw │ Bloomfilter idx: event_time │ doc_meter_metadata(维度参考) │ ▼ Studio Task: */30 min doc_silver_hourly_load(Dynamic Table) avg/max/min load_mw · tariff_period LAG / LEAD · ROWS BETWEEN rolling avg │ ▼ Studio Task: daily 01:00 doc_gold_daily_load_profile(Dynamic Table) daily peak/valley · spread · ratio · season │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ Load Curve BI Peak-Valley External Function Dashboard Pricing System Prophet Forecast


注意事项

  • Dynamic Table 不写 REFRESH INTERVAL:本文所有 Dynamic Table 均未在 DDL 中写

    REFRESH INTERVAL
    REFRESH INTERVAL
    ,刷新调度通过 Studio Task 统一管理,这样可以在同一任务上附加监控告警、数据质量检查等规则,也方便调整刷新频率而不用重建 DT。

  • Bloomfilter Index 只对新数据生效

    CREATE BLOOMFILTER INDEX
    CREATE BLOOMFILTER INDEX
    对创建后写入的新数据生效;若表中已有大量存量数据,建议结合
    BUILD INDEX
    BUILD INDEX
    覆盖存量(
    BLOOMFILTER
    BLOOMFILTER
    类型暂不支持
    BUILD INDEX
    BUILD INDEX
    ,如需覆盖存量需重建表)。

  • Silver 层聚合的幂等性:Silver 层 Dynamic Table 对 Bronze 层按

    DATE_TRUNC('hour', event_time)
    DATE_TRUNC('hour', event_time)
    分组,如果 Bronze 层数据包含同一小时的多条记录(如分钟级数据),
    AVG
    AVG
    /
    MAX
    MAX
    /
    MIN
    MIN
    能正确聚合。若 Bronze 层使用
    INSERT OVERWRITE
    INSERT OVERWRITE
    写入,会导致 Dynamic Table 退化为全量刷新,建议使用追加写(
    INSERT INTO
    INSERT INTO
    )。

  • Z-score 的日内局限性:本文 Z-score 以当日均值和标准差为基准,适合检测当日内的异常时段。若需跨多日检测(如对比历史同期),需改用多日窗口统计,例如同小时点的历史均值和标准差。

  • 冬夏对比解读限制:本文数据集仅包含 2018 年 1 月和 7 月各两天,统计结论仅供说明分析方法;实际生产中应使用完整年度数据(PJM 数据集有 2002–2018 年约 14 万行)以获得稳健结论。

  • 峰谷时段可按业务调整:本文使用 09:00–21:59 为峰时,可根据实际电价政策(如居民峰谷时段与工商业不同)在 Silver 层 DDL 的

    CASE WHEN
    CASE WHEN
    中修改,无需变更下游 Gold 层逻辑。


相关文档

联系我们
预约咨询
微信咨询
电话咨询
邮件咨询