文章目录
- [1. 什么是"维度上卷"](#1. 什么是“维度上卷”)
- [2. ETL 中上卷的核心是"改变事实粒度"](#2. ETL 中上卷的核心是“改变事实粒度”)
- [3. ETL 中上卷的完整流程](#3. ETL 中上卷的完整流程)
-
- [3.1 数据质量检查](#3.1 数据质量检查)
- [3.2 加载维表](#3.2 加载维表)
-
- [地域维表 DimGeography](#地域维表 DimGeography)
- [日期维表 DimDate](#日期维表 DimDate)
- [产品表 DimProduct](#产品表 DimProduct)
- [3.3 生成明细事实表](#3.3 生成明细事实表)
- [3.4 预聚合上卷](#3.4 预聚合上卷)
-
- [上卷一:Date → Month](#上卷一:Date → Month)
- [上卷二:City → Province](#上卷二:City → Province)
- [上卷三:Province → Country](#上卷三:Province → Country)
- [3.5 一致性校验](#3.5 一致性校验)
我的网站原文: https://eleanora-lyh.github.io/MyLearningNotes/
csdn处的文章会尽快同步更新,欢迎大家来访问!
1. 什么是"维度上卷"
维度上卷 Roll-up 本质上是维度建模和 OLAP 中的分析概念;ETL 中的维度上卷,是通过数据转换和聚合,将细粒度事实数据加工为更高层级、更粗粒度的汇总数据。
- Roll-up 是分析语义
- ETL 是实现手段
- 聚合表、汇总事实表是落地结果
所谓上卷,就是从更细维度层级聚合成更高维度层,例如:
-
时间维度:日 → 月 → 季度 → 年
-
地域维度:城市 → 省份 → 国家
-
产品维度:SKU → SubCategory → Category
例如原始数据是:
| Date | City | Province | Country | Product | Revenue |
|---|---|---|---|---|---|
| 2026-08-01 | City1 | Province1 | Country1 | A | 100 |
| 2026-08-02 | City3 | Province2 | Country1 | A | 150 |
| 2026-08-01 | City2 | Province1 | Country1 | A | 80 |
经过时间和地域上卷(C1/C2都属于P1):
| Month | Province | Product | Revenue |
|---|---|---|---|
| 2026-08 | Province1 | A | 180 |
| 2026-08 | Province12 | A | 150 |
这里发生了两个变化:
Date → MonthCity → Province
同时:Revenue = SUM(100 + 150 + 80) = 330
2. ETL 中上卷的核心是"改变事实粒度"
上卷不能只理解为 GROUP BY。
它真正改变的是:
事实表中一行数据所代表的业务范围。
例如:
| 状态 | 一行含义 | 粒度 |
|---|---|---|
| 上卷前 | 某一天 + 某城市 + 某SKU 的销售情况 | Date + City + SKU |
| 上卷后 | 某个月 + 某省份 + 某产品类别的销售情况 | Month + Province + Category |
事实表中的维度键决定事实数据的粒度。事实可以存储在更高粒度,但高粒度事实通常无法再准确拆分回更细粒度
即 日级 → 月级 通常是可完成的;
但 月级 → 日级 如果没有保留日级明细,无法准确恢复。
3. ETL 中上卷的完整流程
#mermaid-svg-C5LBh4Nftdx0r9Sj{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-C5LBh4Nftdx0r9Sj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C5LBh4Nftdx0r9Sj .error-icon{fill:#552222;}#mermaid-svg-C5LBh4Nftdx0r9Sj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C5LBh4Nftdx0r9Sj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .marker.cross{stroke:#333333;}#mermaid-svg-C5LBh4Nftdx0r9Sj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C5LBh4Nftdx0r9Sj p{margin:0;}#mermaid-svg-C5LBh4Nftdx0r9Sj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster-label text{fill:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster-label span{color:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster-label span p{background-color:transparent;}#mermaid-svg-C5LBh4Nftdx0r9Sj .label text,#mermaid-svg-C5LBh4Nftdx0r9Sj span{fill:#333;color:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .node rect,#mermaid-svg-C5LBh4Nftdx0r9Sj .node circle,#mermaid-svg-C5LBh4Nftdx0r9Sj .node ellipse,#mermaid-svg-C5LBh4Nftdx0r9Sj .node polygon,#mermaid-svg-C5LBh4Nftdx0r9Sj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .rough-node .label text,#mermaid-svg-C5LBh4Nftdx0r9Sj .node .label text,#mermaid-svg-C5LBh4Nftdx0r9Sj .image-shape .label,#mermaid-svg-C5LBh4Nftdx0r9Sj .icon-shape .label{text-anchor:middle;}#mermaid-svg-C5LBh4Nftdx0r9Sj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .rough-node .label,#mermaid-svg-C5LBh4Nftdx0r9Sj .node .label,#mermaid-svg-C5LBh4Nftdx0r9Sj .image-shape .label,#mermaid-svg-C5LBh4Nftdx0r9Sj .icon-shape .label{text-align:center;}#mermaid-svg-C5LBh4Nftdx0r9Sj .node.clickable{cursor:pointer;}#mermaid-svg-C5LBh4Nftdx0r9Sj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .arrowheadPath{fill:#333333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C5LBh4Nftdx0r9Sj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-C5LBh4Nftdx0r9Sj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C5LBh4Nftdx0r9Sj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster text{fill:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj .cluster span{color:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj 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-C5LBh4Nftdx0r9Sj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-C5LBh4Nftdx0r9Sj rect.text{fill:none;stroke-width:0;}#mermaid-svg-C5LBh4Nftdx0r9Sj .icon-shape,#mermaid-svg-C5LBh4Nftdx0r9Sj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C5LBh4Nftdx0r9Sj .icon-shape p,#mermaid-svg-C5LBh4Nftdx0r9Sj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-C5LBh4Nftdx0r9Sj .icon-shape .label rect,#mermaid-svg-C5LBh4Nftdx0r9Sj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C5LBh4Nftdx0r9Sj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-C5LBh4Nftdx0r9Sj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-C5LBh4Nftdx0r9Sj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-C5LBh4Nftdx0r9Sj .step>*{fill:#FFE08A!important;stroke:#A86700!important;color:#3D2900!important;stroke-width:2px!important;}#mermaid-svg-C5LBh4Nftdx0r9Sj .step span{fill:#FFE08A!important;stroke:#A86700!important;color:#3D2900!important;stroke-width:2px!important;}#mermaid-svg-C5LBh4Nftdx0r9Sj .step tspan{fill:#3D2900!important;} 原始数据
Step1: 数据质量检查
检查 City → Province 是否唯一
检查 Province → Country 是否唯一
识别缺失、错误和历史变更
Step2: 加载维表
DimDate
DimGeography
DimProduct
Step3: 生成明细事实表
FactRevenueDaily
Step4: 预聚合上卷
FactRevenueMonthlyCity
FactRevenueMonthlyProvince
FactRevenueMonthlyCountry
Step5: 一致性校验
各级 Revenue 总和均应为 330
3.1 数据质量检查
检查 City → Province
| City | Province | 是否唯一 |
|---|---|---|
| City1 | Province1 | 是 |
| City2 | Province1 | 是 |
| City3 | Province2 | 是 |
当前数据中,每个 City 只属于一个 Province,因此层级关系正常。
检查 Province → Country
| Province | Country | 是否唯一 |
|---|---|---|
| Province1 | Country1 | 是 |
| Province2 | Country1 | 是 |
最终可以建立稳定的地理层级:
#mermaid-svg-DcpqhTOEfwKWftYL{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-DcpqhTOEfwKWftYL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DcpqhTOEfwKWftYL .error-icon{fill:#552222;}#mermaid-svg-DcpqhTOEfwKWftYL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DcpqhTOEfwKWftYL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DcpqhTOEfwKWftYL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DcpqhTOEfwKWftYL .marker.cross{stroke:#333333;}#mermaid-svg-DcpqhTOEfwKWftYL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DcpqhTOEfwKWftYL p{margin:0;}#mermaid-svg-DcpqhTOEfwKWftYL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DcpqhTOEfwKWftYL .cluster-label text{fill:#333;}#mermaid-svg-DcpqhTOEfwKWftYL .cluster-label span{color:#333;}#mermaid-svg-DcpqhTOEfwKWftYL .cluster-label span p{background-color:transparent;}#mermaid-svg-DcpqhTOEfwKWftYL .label text,#mermaid-svg-DcpqhTOEfwKWftYL span{fill:#333;color:#333;}#mermaid-svg-DcpqhTOEfwKWftYL .node rect,#mermaid-svg-DcpqhTOEfwKWftYL .node circle,#mermaid-svg-DcpqhTOEfwKWftYL .node ellipse,#mermaid-svg-DcpqhTOEfwKWftYL .node polygon,#mermaid-svg-DcpqhTOEfwKWftYL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DcpqhTOEfwKWftYL .rough-node .label text,#mermaid-svg-DcpqhTOEfwKWftYL .node .label text,#mermaid-svg-DcpqhTOEfwKWftYL .image-shape .label,#mermaid-svg-DcpqhTOEfwKWftYL .icon-shape .label{text-anchor:middle;}#mermaid-svg-DcpqhTOEfwKWftYL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DcpqhTOEfwKWftYL .rough-node .label,#mermaid-svg-DcpqhTOEfwKWftYL .node .label,#mermaid-svg-DcpqhTOEfwKWftYL .image-shape .label,#mermaid-svg-DcpqhTOEfwKWftYL .icon-shape .label{text-align:center;}#mermaid-svg-DcpqhTOEfwKWftYL .node.clickable{cursor:pointer;}#mermaid-svg-DcpqhTOEfwKWftYL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DcpqhTOEfwKWftYL .arrowheadPath{fill:#333333;}#mermaid-svg-DcpqhTOEfwKWftYL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DcpqhTOEfwKWftYL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DcpqhTOEfwKWftYL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DcpqhTOEfwKWftYL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DcpqhTOEfwKWftYL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DcpqhTOEfwKWftYL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DcpqhTOEfwKWftYL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DcpqhTOEfwKWftYL .cluster text{fill:#333;}#mermaid-svg-DcpqhTOEfwKWftYL .cluster span{color:#333;}#mermaid-svg-DcpqhTOEfwKWftYL 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-DcpqhTOEfwKWftYL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DcpqhTOEfwKWftYL rect.text{fill:none;stroke-width:0;}#mermaid-svg-DcpqhTOEfwKWftYL .icon-shape,#mermaid-svg-DcpqhTOEfwKWftYL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DcpqhTOEfwKWftYL .icon-shape p,#mermaid-svg-DcpqhTOEfwKWftYL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DcpqhTOEfwKWftYL .icon-shape .label rect,#mermaid-svg-DcpqhTOEfwKWftYL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DcpqhTOEfwKWftYL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DcpqhTOEfwKWftYL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DcpqhTOEfwKWftYL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} City1
Province1
City2
City3
Province2
Country1
3.2 加载维表
不建议在事实表里重复保存 City、Province、Country 名称。所以按照星型模型可以拆分事实数据与维度数据:
-
地域维表
DimGeography -
日期维表
DimDate -
产品表
DimProduct
地域维表 DimGeography
| GeographyKey | City | Province | Country |
|---|---|---|---|
| 101 | City1 | Province1 | Country1 |
| 102 | City3 | Province2 | Country2 |
| 103 | City2 | Province1 | Country1 |
日期维表 DimDate
| DateKey | Date | Month | Quarter | Year |
|---|---|---|---|---|
| 20260801 | 2026-08-01 | 2026-08 | 2026-Q3 | 2026 |
| 20260802 | 2026-08-02 | 2026-08 | 2026-Q3 | 2026 |
产品表 DimProduct
| ProductKey | Product |
|---|---|
| 201 | A |
如果存在产品类别,还可以设计为:Product → ProductCategory → ProductLine
3.3 生成明细事实表
- 明细事实表
FactRevenueDaily
| DateKey | GeographyKey | ProductKey | Revenue |
|---|---|---|---|
| 20260801 | 101 | 201 | 100 |
| 20260802 | 103 | 201 | 150 |
| 20260801 | 102 | 201 | 80 |
粒度仍然是:Date + GeographyKey + Product
City、Province、Country 名称不需要重复保存在事实表中,而是通过 GeographyKey 关联 DimGeography
3.4 预聚合上卷
上卷一:Date → Month
目标粒度:Month + City + Product,生成 FactRevenueMonthlyCity
| Month | City | Product | Revenue |
|---|---|---|---|
| 2026-08 | City1 | A | 100 |
| 2026-08 | City2 | A | 80 |
| 2026-08 | City3 | A | 150 |
这里只提升了时间层级:Date → Month
City 维度仍保留在原层级
上卷二:City → Province
目标粒度:Month + Province + Product,生成 FactRevenueMonthlyProvince
| Month | Province | Product | Revenue |
|---|---|---|---|
| 2026-08 | Province1 | A | 180 |
| 2026-08 | Province2 | A | 150 |
计算过程:
plain
Province1 = City1 100 + City2 80 = 180
Province2 = City3 150
这里同时发生:
Date → Month
City → Province
上卷三:Province → Country
目标粒度:Month + Country + Product,生成 FactRevenueMonthlyCountry
| Month | Country | Product | Revenue |
|---|---|---|---|
| 2026-08 | Country1 | A | 330 |
计算过程:
Plain
Country1 = Province1 180 + Province2 150 = 330
完整上卷路径为:
Date → Month
City → Province → Country
3.5 一致性校验
| 层级 | Revenue 总和 |
|---|---|
| 原始明细数据 | 330 |
| Monthly City | 330 |
| Monthly Province | 330 |
| Monthly Country | 330 |
因此满足:
plain
明细层 Revenue
= 城市层 Revenue
= 省份层 Revenue
= 国家层 Revenue
= 330
最终的数据流可以概括为:
#mermaid-svg-ZRwDc5KEOkzcYCXC{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-ZRwDc5KEOkzcYCXC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZRwDc5KEOkzcYCXC .error-icon{fill:#552222;}#mermaid-svg-ZRwDc5KEOkzcYCXC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZRwDc5KEOkzcYCXC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .marker.cross{stroke:#333333;}#mermaid-svg-ZRwDc5KEOkzcYCXC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZRwDc5KEOkzcYCXC p{margin:0;}#mermaid-svg-ZRwDc5KEOkzcYCXC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster-label text{fill:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster-label span{color:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster-label span p{background-color:transparent;}#mermaid-svg-ZRwDc5KEOkzcYCXC .label text,#mermaid-svg-ZRwDc5KEOkzcYCXC span{fill:#333;color:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .node rect,#mermaid-svg-ZRwDc5KEOkzcYCXC .node circle,#mermaid-svg-ZRwDc5KEOkzcYCXC .node ellipse,#mermaid-svg-ZRwDc5KEOkzcYCXC .node polygon,#mermaid-svg-ZRwDc5KEOkzcYCXC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .rough-node .label text,#mermaid-svg-ZRwDc5KEOkzcYCXC .node .label text,#mermaid-svg-ZRwDc5KEOkzcYCXC .image-shape .label,#mermaid-svg-ZRwDc5KEOkzcYCXC .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZRwDc5KEOkzcYCXC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .rough-node .label,#mermaid-svg-ZRwDc5KEOkzcYCXC .node .label,#mermaid-svg-ZRwDc5KEOkzcYCXC .image-shape .label,#mermaid-svg-ZRwDc5KEOkzcYCXC .icon-shape .label{text-align:center;}#mermaid-svg-ZRwDc5KEOkzcYCXC .node.clickable{cursor:pointer;}#mermaid-svg-ZRwDc5KEOkzcYCXC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .arrowheadPath{fill:#333333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZRwDc5KEOkzcYCXC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZRwDc5KEOkzcYCXC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZRwDc5KEOkzcYCXC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster text{fill:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC .cluster span{color:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC 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-ZRwDc5KEOkzcYCXC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZRwDc5KEOkzcYCXC rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZRwDc5KEOkzcYCXC .icon-shape,#mermaid-svg-ZRwDc5KEOkzcYCXC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZRwDc5KEOkzcYCXC .icon-shape p,#mermaid-svg-ZRwDc5KEOkzcYCXC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZRwDc5KEOkzcYCXC .icon-shape .label rect,#mermaid-svg-ZRwDc5KEOkzcYCXC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZRwDc5KEOkzcYCXC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZRwDc5KEOkzcYCXC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZRwDc5KEOkzcYCXC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 明细事实:Date + City + Product
月度城市汇总:Month + City + Product
月度省份汇总:Month + Province + Product
月度国家汇总:Month + Country + Product
这就是 ETL 中维度上卷的核心:维度层级向上提升,事实粒度变粗,同时按照目标粒度重新聚合度量值。