1. 这不是简单的“GROUP BY”——多维聚合中的数据变形术到底在解决什么问题你有没有遇到过这样的场景销售部门要按地区产品线季度三个维度看毛利财务却要求按成本中心会计期间费用类型汇总预算执行率而管理层日报又得把前两者合并成“业务单元×时间粒度×绩效指标”的交叉表这时候Excel 的数据透视表点到第三层就开始卡顿SQL 里嵌套的 GROUP BY 加上 CASE WHEN 堆到半屏就让人头皮发紧Pandas 的groupby().agg()写完发现结果形状和下游 BI 工具要求的完全对不上——不是少了一维索引就是聚合后字段名乱码再或者空值处理逻辑一改整个链路就崩。这正是“多维聚合中的数据操作”Data Manipulation in Multi-Dimensional Aggregation真正要啃的硬骨头它根本不是教你怎么算平均值或求和而是解决高维结构化数据在聚合过程中如何保持语义完整性、维度可追溯性、形态可适配性这一系列连锁问题。核心关键词——多维聚合、数据变形、维度对齐、层级折叠、交叉展开、聚合后重塑——每一个都直指实际业务中反复踩坑的现场。这篇文章面向的是已经能写基础 SQL 和 Pandas 的中级数据从业者你不需要从零学 groupby但需要知道为什么df.groupby([A,B]).sum()输出的 DataFrame 索引是 MultiIndex 而不是普通列为什么 Power BI 里拖一个“年份-季度”层次进去自动切片而你用 Python 手动拼的字符串却总在钻取时断掉。我会全程用真实项目里的代码片段、报错截图、配置参数和调试日志说话不讲抽象概念只拆解“当时我为什么这么写”“换一种写法会掉进什么坑”“生产环境跑崩前三秒发生了什么”。如果你正被报表口径不一致、ETL 链路频繁返工、或者 BI 工具里维度下钻失效这些问题困扰这篇就是为你写的实操手册。2. 多维聚合的本质不是“分组计算”而是“维度空间的坐标系重构”2.1 为什么传统 GROUP BY 在多维场景下天然失能先说个反直觉的事实SQL 标准里的GROUP BY语法本身并不原生支持多维语义。它只是把指定列的组合值作为分组键然后对每组做聚合。比如SELECT region, product_line, quarter, SUM(revenue) FROM sales GROUP BY region, product_line, quarter表面看是三维但数据库引擎内部只把它当作一个三元组(region, product_line, quarter)的哈希键来处理。它不会自动理解“quarter 是 time 维度下的子层级”“product_line 隶属于 product_category 维度”更不会帮你处理“当某 region 某 quarter 没有 sales 记录时是否要补 0 行以保持立方体完整性”。这种“扁平化分组”在二维报表里够用一旦进入真正的 OLAP 场景——比如构建销售立方体Sales Cube你就必须面对三个刚性需求维度层级Hierarchy时间不能只是“2023-Q1”它必须能向上钻取到“2023”向下钻取到“2023-Jan”产品不能只是“iPhone 15”它必须能归属到“Smartphone → Apple → Consumer Electronics”这条路径维度成员完整性Member Completeness即使某区域某季度没卖一台手机报表里也得显示“0”否则同比环比计算全错聚合后形态适配Shape Adaptation下游系统可能要求输入是宽表每个季度一列也可能要求是长表quarter 列 value 列还可能要求是嵌套 JSON按 region 分组内含 product_line 数组。提示很多团队用“先 GROUP BY 出结果再用 Python/PivotTable 补零、转宽表”来绕开这看似简单实则埋下巨大隐患——补零逻辑散落在各处没人知道“哪些维度组合必须存在”更没人维护“当新增一个 region 时历史所有季度要不要自动补行”。这就是为什么专业 OLAP 引擎如 Apache Kylin、Microsoft Analysis Services都内置了维度建模Dimensional Modeling模块而不是依赖 SQL 的 GROUP BY。2.2 多维聚合的数据操作 维度建模 × 聚合计算 × 形态转换我把整个流程拆成三个不可分割的环节缺一不可维度建模Dimension Modeling定义维度表Dim_Time、Dim_Product、事实表Fact_Sales、以及它们之间的关系星型模型。关键动作是给每个维度字段打上“层级标签”Level Tag比如time_key标为level: quarteryear_month标为level: monthfiscal_year标为level: year。这不是加注释而是为后续自动钻取提供元数据依据。聚合计算Aggregation Computation在事实表上按维度层级组合进行预计算。注意这里不是一次GROUP BY而是层级感知的聚合。例如要同时支持“按季度汇总”和“按年汇总”理想方案不是分别跑两条 SQL而是用ROLLUP或CUBE生成包含所有层级组合的结果集再由上层逻辑按需裁剪。PostgreSQL 的GROUPING SETS ((region),(region,product_line),(region,product_line,quarter))就是典型实现。形态转换Shape Transformation把聚合结果从“数据库行集合”变成“业务可消费形态”。这步最易被忽视却是出问题最多的环节。比如BI 工具需要宽表把(region, product_line, quarter, revenue)转成(region, product_line, Q1_2023, Q2_2023, Q3_2023, Q4_2023)API 接口需要嵌套 JSON{region: North, products: [{name: iPhone, revenue: 1200000, quarters: [{q: Q1, v: 300000}, ...]}]}机器学习特征工程需要长表 时间滞后在(region, quarter, revenue)基础上增加revenue_lag1,revenue_lag2等列。这三个环节环环相扣。维度建模错了聚合结果就无法正确钻取聚合没覆盖全层级形态转换时就会缺数据形态转换逻辑硬编码在应用层一旦维度变更所有调用方都要改。我在某零售客户项目里就吃过亏他们最初用 Pandaspivot_table直接转宽表后来新增“门店等级”维度结果 7 个业务系统里有 5 个忘了改 pivot 的columns参数导致大促期间所有区域经理看到的都是错的“单店坪效”。2.3 为什么必须放弃“一条 SQL 解决所有”的幻想很多人试图用一条巨复杂的 SQL 搞定一切比如SELECT region, product_line, quarter, SUM(revenue) as total_revenue, -- 补零用 LEFT JOIN 维度表确保全组合 COALESCE(SUM(revenue), 0) as revenue_filled, -- 转宽表用 CASE WHEN 拼列仅示意实际会超长 SUM(CASE WHEN quarter Q1 THEN revenue END) as q1_rev, SUM(CASE WHEN quarter Q2 THEN revenue END) as q2_rev, -- 计算同比需要自连接或窗口函数 (SUM(revenue) - LAG(SUM(revenue), 4) OVER (PARTITION BY region, product_line ORDER BY quarter)) / NULLIF(LAG(SUM(revenue), 4) OVER (PARTITION BY region, product_line ORDER BY quarter), 0) as yoy_growth FROM fact_sales s RIGHT JOIN dim_region r ON s.region_id r.id RIGHT JOIN dim_product p ON s.product_id p.id RIGHT JOIN dim_time t ON s.time_id t.id GROUP BY region, product_line, quarter这段 SQL 看似强大但它违反了三条工程铁律可维护性归零LAG(..., 4)依赖“季度按 Q1-Q2-Q3-Q4 严格排序且无缺失”一旦某季度数据延迟入库整个同比就错扩展性窒息新增一个“促销活动类型”维度所有GROUP BY、JOIN、CASE WHEN全得重写测试用例翻倍职责混乱SQL 同时承担了数据补全LEFT JOIN、形态转换CASE WHEN、业务逻辑同比计算三重任务任何一个环节出问题整条链路不可调试。我的经验是把“聚合计算”和“形态转换”物理隔离。用 SQL 或 Spark SQL 只做一件事——产出标准的、带完整维度层级信息的“聚合基表”Aggregated Base Table字段固定为[dim1, dim2, ..., dimN, measure1, measure2, ...]所有补零、钻取、宽/长转换、指标计算全部交给 Python 或专用工具如 dbt 的ref()config(materializedtable)在下游完成。这样维度变更只需改一处建模定义形态转换逻辑可版本化、可单元测试、可灰度发布。3. 实操四步法从原始事实表到可交付的多维聚合结果3.1 第一步构建维度完备的事实聚合基表核心是补零与层级对齐这是整个链条的地基90% 的后续问题都源于此步没做好。我们以一个简化但真实的电商销售事实表fact_orders为例它包含order_iduser_idproduct_idregion_idtime_idorder_amountquantity1001u123p456r001t202301299.011002u789p123r002t2023021999.02维度表dim_time包含time_idyearquartermonthfiscal_periodis_holidayt2023012023Q1JanFP2023Q1falset2023022023Q2FebFP2023Q2true目标产出按region地区、quarter季度、is_holiday是否节假日三个维度聚合的total_revenue和order_count且必须保证所有 region × quarter × is_holiday 组合都存在缺失则补 0。关键陷阱直接LEFT JOIN dim_time不够因为dim_time里可能有t202303Q3但fact_orders里没有该时间的订单此时LEFT JOIN会保留t202303的维度记录但region_id和product_id是 NULL导致GROUP BY时这些 NULL 组合无法被识别为有效维度成员。正确做法是先生成所有合法的维度组合笛卡尔积再 LEFT JOIN 事实表。在 SQL 中用CROSS JOIN构建全组合-- Step 1: 生成全维度组合笛卡尔积 WITH full_combinations AS ( SELECT r.region_id, r.region_name, t.time_id, t.quarter, t.is_holiday FROM dim_region r CROSS JOIN ( SELECT DISTINCT quarter, is_holiday, time_id FROM dim_time WHERE year 2023 -- 限定分析年份避免爆炸 ) t ), -- Step 2: 与事实表关联补零 aggregated_base AS ( SELECT fc.region_id, fc.region_name, fc.quarter, fc.is_holiday, COALESCE(SUM(o.order_amount), 0) as total_revenue, COALESCE(COUNT(o.order_id), 0) as order_count FROM full_combinations fc LEFT JOIN fact_orders o ON fc.region_id o.region_id AND fc.time_id o.time_id GROUP BY fc.region_id, fc.region_name, fc.quarter, fc.is_holiday ) SELECT * FROM aggregated_base;这个aggregated_base就是我们的“聚合基表”。它有四个关键特征维度纯净region_id,region_name,quarter,is_holiday全是明确的维度属性无 NULL度量确定total_revenue,order_count是原子聚合结果无业务逻辑污染组合完备CROSS JOIN确保了所有region × quarter × is_holiday组合都存在层级显式quarter和is_holiday来自dim_time天然携带时间维度层级信息。实操心得我在金融风控项目里曾跳过CROSS JOIN直接LEFT JOIN dim_time结果发现“某分行在某季度无放款记录”时该分行该季度在报表里直接消失导致监管报送的“零放款分行数”统计错误。补救时花了两天回溯所有 ETL 脚本就为了加这 3 行CROSS JOIN代码。记住多维聚合的第一守则是“宁可多一行不可少一维”。3.2 第二步用 Pandas 进行维度层级折叠与钻取模拟替代 SQL 的 ROLLUPSQL 的ROLLUP很好用但它的输出是扁平化的比如GROUP BY region, product_line WITH ROLLUP会产出(r001,p123),(r001,NULL),(NULL,NULL)三行其中NULL代表“总计”但下游程序很难区分“这个 NULL 是维度值缺失还是人为聚合的总计”。Pandas 提供了更语义清晰的方式pd.crosstab和pd.pivot_table的margins参数配合MultiIndex的droplevel操作可以精确控制每一层的聚合。继续上面的例子我们现在有aggregated_baseDataFrame想支持两种视图视图 A地区-季度钻取点击“华东”区域下钻看其下所有季度的total_revenue视图 B全公司总计不按任何维度分组只看total_revenue总和。用 Pandas 实现import pandas as pd import numpy as np # 假设 df_base 是从 SQL 查询得到的 aggregated_base DataFrame # df_base.columns: [region_id, region_name, quarter, is_holiday, total_revenue, order_count] # 1. 设置 MultiIndex明确维度层级顺序重要顺序决定钻取路径 df_indexed df_base.set_index([region_id, region_name, quarter, is_holiday]) # 2. 生成“地区-季度”钻取视图视图A # 方法对 index 的前两层region_id, region_name分组sum 所有度量 view_a df_indexed.groupby(level[region_id, region_name]).sum() # view_a.shape: (n_regions, 2) - 每个地区一行total_revenue 和 order_count 两列 # 3. 生成“全公司总计”视图视图B # 方法drop 所有维度层级直接 sum view_b df_indexed.sum(numeric_onlyTrue) # view_b 是 Series: total_revenue 12345678.0, order_count 98765 # 4. 更高级模拟 ROLLUP 效果生成带小计的宽表 # 比如按 region_name 行quarter 列值为 total_revenue并在最后一行加“总计” pivot_w_margins pd.pivot_table( df_base, valuestotal_revenue, indexregion_name, columnsquarter, aggfuncsum, marginsTrue, # 自动加 All 行和 All 列 fill_value0 ) # pivot_w_margins.shape: (n_regions 1, n_quarters 1) # 最后一行是 All最后一列是 All右下角是 grand total这里的关键洞察是Pandas 的groupby(level...)操作本质上是在对维度空间的坐标系进行“降维投影”。level[region_id, region_name]投影到地区层面level[]空列表就是投影到零维——即全空间。这比 SQL 的ROLLUP更可控因为你可以明确指定“我要从四维空间投影到二维空间”而不是让数据库引擎猜你要什么。注意pd.pivot_table的marginsTrue生成的All行/列其索引名是字符串All不是NaN。这很重要因为 BI 工具如 Tableau能识别All作为特殊汇总标识而NaN会被当成脏数据过滤掉。我在某 SaaS 客户项目里因用了fillna(All)而非marginsTrue导致 Tableau 的“显示总计”功能始终不生效排查了三天才发现是字符串All和数值NaN的语义差异。3.3 第三步形态转换——宽表、长表、嵌套 JSON 的无损互转业务系统永远在变今天要宽表明天要 JSON后天又要长表喂给 Prophet 做预测。如果每次形态转换都重写逻辑不出三个月就崩溃。我的方案是定义一个“标准中间形态”Canonical Shape所有转换都围绕它进行。我选的是“长表 维度列 度量列”结构因为它最接近事实表的原始语义且转换代价最小。标准中间形态示例canonical_dfregion_namequarteris_holidaymetric_namemetric_value华东Q1Falsetotal_revenue1234567.0华东Q1Falseorder_count8765华东Q1Truetotal_revenue987654.0...............从aggregated_base宽表转到canonical_df长表# 从宽表 df_base 转长表 canonical_df # df_base: [region_name, quarter, is_holiday, total_revenue, order_count] canonical_df df_base.melt( id_vars[region_name, quarter, is_holiday], value_vars[total_revenue, order_count], var_namemetric_name, value_namemetric_value ) # canonical_df.shape: (n_rows * 2, 5)从canonical_df转宽表按 region_name 行quarter 列每个 metric 一个子表# 为每个 metric 生成独立宽表 for metric in [total_revenue, order_count]: wide_metric canonical_df[canonical_df[metric_name] metric].pivot( indexregion_name, columnsquarter, valuesmetric_value ).fillna(0).astype(int) print(f\n {metric} Wide Table ) print(wide_metric)从canonical_df转嵌套 JSON按 region_name 分组内含 quarter 数组import json def to_nested_json(df): result [] for region, region_group in df.groupby(region_name): quarters [] for quarter, q_group in region_group.groupby(quarter): # 每个 quarter 下收集所有 metric metrics {} for _, row in q_group.iterrows(): metrics[row[metric_name]] float(row[metric_value]) quarters.append({ quarter: quarter, metrics: metrics, is_holiday: bool(q_group[is_holiday].iloc[0]) # 假设同 quarter 的 is_holiday 一致 }) result.append({ region_name: region, quarters: quarters }) return json.dumps(result, ensure_asciiFalse, indent2) # 调用 json_output to_nested_json(canonical_df) print(json_output[:500] ...) # 打印前500字符这个canonical_df就是你的“瑞士军刀”。所有新需求——比如“要按 is_holiday 分组看同比”“要导出 CSV 给财务”“要生成 GraphQL 查询响应”——都只需写一个从canonical_df出发的转换函数无需碰原始聚合逻辑。我在某跨境电商项目里用这套模式支撑了 12 个不同业务方的报表需求当市场部突然要求“按促销活动类型渠道来源”双维度看 ROI 时我只加了 8 行代码新增两个id_vars字段20 分钟就上线了新报表。3.4 第四步注入业务逻辑——在形态转换层安全地计算指标最后一步也是最容易出错的一步把“聚合结果”变成“业务指标”。比如total_revenue是聚合结果但“毛利率”(total_revenue - total_cost) / total_revenue就是业务指标它依赖多个度量且有除零风险。错误做法在 SQL 层直接写(SUM(revenue)-SUM(cost))/SUM(revenue)。问题在于如果某 region × quarter 的SUM(revenue)是 0整个表达式返回NULL或报错且这个错误会污染所有下游。正确做法在 Pandas 的形态转换层用向量化操作安全计算并显式处理边界# 假设 canonical_df 已包含 total_revenue 和 total_cost 两行 # 先 pivot 回宽表以便计算 wide_df canonical_df.pivot_table( index[region_name, quarter, is_holiday], columnsmetric_name, valuesmetric_value, aggfuncfirst ).reset_index() # 安全计算毛利率 wide_df[gross_margin] np.where( wide_df[total_revenue] ! 0, (wide_df[total_revenue] - wide_df[total_cost]) / wide_df[total_revenue], 0.0 # 明确指定收入为0时毛利率为0业务约定 ) # 再转回 canonical_df添加新 metric gm_series wide_df.melt( id_vars[region_name, quarter, is_holiday], value_vars[gross_margin], var_namemetric_name, value_namemetric_value ) canonical_df_with_gm pd.concat([canonical_df, gm_series], ignore_indexTrue)这里用了np.where而不是pd.Series.div因为前者可以精确控制条件分支后者在分母为 0 时默认返回inf或NaN需要额外replace。更重要的是0.0是业务决策不是技术妥协——财务团队确认过“无收入即无毛利填 0 比填 NaN 更符合报表惯例”。实操心得在某制造业客户项目里我们曾用div计算“设备综合效率 OEE”结果某产线某天停机availability为 0OEE 算出来是infBI 工具直接崩溃。后来改成np.where(availability 0, (performance * quality * availability), 0)并加了单元测试验证所有边界情况才稳定下来。所有业务指标计算必须有明确的、可审计的边界处理策略写在代码里而不是记在人脑里。4. 高频问题排查与避坑指南那些让你加班到凌晨三点的“幽灵 Bug”4.1 问题Pivot 后出现大量 NaN但原始数据明明有值现象用pd.pivot_table(df, indexregion, columnsquarter, valuesrevenue)结果大部分单元格是NaN检查df[region].unique()和df[quarter].unique()都正常。根因排查第一步检查df是否有重复索引。pivot_table默认对重复(index, columns)组合做aggfuncmean但如果aggfunc是sum且你没指定它仍会尝试聚合而聚合前若数据类型不匹配如revenue列混有字符串N/A就会静默转为NaN。第二步用df.duplicated(subset[region,quarter]).sum()查重。如果有重复pivot_table会聚合但聚合结果可能因数据类型问题为NaN。第三步检查revenue列数据类型。df[revenue].dtype如果是object很可能混入了非数字字符串。解决方案# 1. 清洗 revenue 列 df[revenue] pd.to_numeric(df[revenue], errorscoerce) # coerce 将非法值转 NaN # 2. 删除重复行或按业务规则去重 df_clean df.drop_duplicates(subset[region,quarter], keeplast) # 3. 显式指定 aggfunc pivot_result pd.pivot_table( df_clean, indexregion, columnsquarter, valuesrevenue, aggfuncsum, # 明确指定避免歧义 fill_value0 # 缺失组合填 0而非 NaN )注意fill_value0是救命参数它把(region, quarter)组合不存在时的NaN替换为0这比在下游用fillna(0)更高效因为pivot_table内部直接处理。4.2 问题MultiIndex 的 level 名称丢失groupby 报错 “KeyError: region_name”现象df.set_index([region_name,quarter])后df.groupby(region_name).sum()报错KeyError。原因set_index后region_name不再是列column而是索引index的一部分。groupby默认在列上找 key找不到就报错。正确操作# 方案1用 level 名称推荐语义清晰 df_grouped df.groupby(levelregion_name).sum() # 方案2重置索引再 groupby 列 df_reset df.reset_index() df_grouped df_reset.groupby(region_name).sum() # 方案3用 xscross-section切片但仅适用于单层筛选 # df.xs(华东, levelregion_name) # 获取华东的所有行避坑技巧永远用df.index.names和df.columns检查当前结构。print(df.index.names)会输出[region_name, quarter]print(df.columns)会输出Index([revenue, order_count], dtypeobject)。只要养成这个习惯90% 的索引相关错误一眼就能定位。4.3 问题CROSS JOIN 导致内存爆炸查询超时现象dim_region有 1000 行dim_time有 10000 行CROSS JOIN产生 1000 万行数据库内存溢出。解决方案分治 过滤前置。不要一次性CROSS JOIN所有维度而是先缩小范围-- 错误全量 CROSS JOIN SELECT * FROM dim_region CROSS JOIN dim_time; -- 1000 * 10000 10M -- 正确先过滤再 JOIN WITH filtered_time AS ( SELECT DISTINCT time_id, quarter, is_holiday FROM dim_time WHERE year BETWEEN 2022 AND 2023 -- 只取两年 ), filtered_region AS ( SELECT region_id, region_name FROM dim_region WHERE status active -- 只取有效区域 ) SELECT * FROM filtered_region CROSS JOIN filtered_time; -- 500 * 200 100K在 Pandas 中用pd.merge的howcross也要谨慎优先用itertools.product生成小规模组合from itertools import product import pandas as pd # 小规模组合内存可控 regions [华东, 华北, 华南] quarters [Q1, Q2, Q3, Q4] combinations list(product(regions, quarters)) df_combos pd.DataFrame(combinations, columns[region_name, quarter])4.4 问题JSON 输出里中文乱码或浮点数精度失控现象json.dumps(df.to_dict(records))输出{region_name: \u534e\u4e1c}或{revenue: 123456.78999999999}。解决方案# 1. 中文不乱码ensure_asciiFalse json_str json.dumps( df.to_dict(records), ensure_asciiFalse, # 关键 indent2 ) # 2. 浮点数精度先 round再转 float df_rounded df.copy() for col in [revenue, margin]: if col in df_rounded.columns: df_rounded[col] df_rounded[col].round(2).astype(float) json_str json.dumps( df_rounded.to_dict(records), ensure_asciiFalse, indent2 )终极建议对所有要序列化的 DataFrame统一走一个to_api_format()函数def to_api_format(df): 标准化 API 输出格式 df_out df.copy() # 1. 处理浮点数 float_cols df_out.select_dtypes(include[float]).columns df_out[float_cols] df_out[float_cols].round(2) # 2. 处理日期 date_cols df_out.select_dtypes(include[datetime]).columns for col in date_cols: df_out[col] df_out[col].dt.strftime(%Y-%m-%d) # 3. 重置索引确保是纯列 if not df_out.index.equals(pd.RangeIndex(len(df_out))): df_out df_out.reset_index(dropTrue) return df_out # 使用 api_ready_df to_api_format(wide_df) json_output json.dumps(api_ready_df.to_dict(records), ensure_asciiFalse, indent2)5. 工具链选型与性能对比什么场景该用 SQL什么场景必须上 Python5.1 SQLPostgreSQL / BigQuery适合“聚合基表”的生成优势原生补零CROSS JOINLEFT JOIN是数据库最擅长的 set operation性能远超应用层循环权限与治理维度表和事实表的访问权限、行级安全RLS都在 DB 层统一管控增量更新友好用INSERT ... ON CONFLICT DO UPDATE可高效更新聚合基表。适用场景数据量 1 亿行且维度组合数 1000 万维度表稳定变更频率低 1 次/月需要与现有 BI 工具如 Looker、Power BI DirectQuery直连。性能提示在dim_time上为year,quarter建复合索引在fact_orders上为region_id,time_id建联合索引。我在某物流项目中加了这两个索引CROSS JOIN查询从 42 秒降到 1.8 秒。5.2 PythonPandas / Polars适合“形态转换”与“业务逻辑注入”优势灵活性无敌pivot_table的margins、stack/unstack、explode等操作SQL 很难优雅实现调试友好df.head()、df.info()、df.describe()一行命令看清数据全貌生态丰富无缝对接plotly可视化、scikit-learn特征工程、fastapiAPI 服务。适用场景需要复杂形态转换如嵌套 JSON、宽表转时序长表业务逻辑多变需快速迭代如每周调整毛利率计算公式数据量 5000 万行且内存充足 32GB RAM。性能警告Pandas 在 1000 万行时groupby和pivot会明显变慢。此时应切换到Polarsimport polars as pl # Polars 语法几乎与 Pandas 一致但速度提升 5-10 倍 df_pl pl.read_sql(SELECT * FROM aggregated_base, connection) pivot_pl df_pl.pivot( valuestotal_revenue, index