Featured image of post DuckDB PIVOT/UNPIVOT 实战:把杂乱数据变成可变现的数据产品

DuckDB PIVOT/UNPIVOT 实战:把杂乱数据变成可变现的数据产品

用 DuckDB 的 PIVOT 和 UNPIVOT 完成行列互转,把原始数据变成老板想要的报表格式。本文不仅讲解语法,更演示如何把这些技巧整合到数据产品中实现变现。

从原始数据到数据产品:行列转换是关键一步

在数据行业摸爬滚打的人都知道,客户给你的数据从来都不是「开箱即用」的。

要么是 ERP 系统导出的宽表——每个月份一列、每个产品一列,密密麻麻;要么是传感器上传的窄表——一条记录一个读数,成千上万条数据堆在一起。

而你的客户(老板、投资人、外部客户)想要的,永远是一种特定格式的报表。

PIVOT 和 UNPIVOT 就是连接「原始数据」和「目标报表」之间的桥梁。今天我不只讲语法,更要演示如何用这两个功能搭建一个真正能赚钱的数据产品。

PIVOT/UNPIVOT 数据转换流程


一、PIVOT 实战:把窄表变成老板要的报表

场景:电商销售周报

假设你在一家电商公司做数据分析。数据库里存的是交易流水(窄表格式):

-- 模拟电商交易流水
CREATE TABLE orders AS
SELECT * FROM (VALUES
    ('2026-06-01', '上海', 'iPhone 15', 7999, 1),
    ('2026-06-01', '北京', 'MacBook Pro', 14999, 1),
    ('2026-06-01', '上海', 'AirPods Pro', 1899, 2),
    ('2026-06-02', '广州', 'iPhone 15', 7999, 1),
    ('2026-06-02', '北京', 'iPad Air', 4799, 1),
    ('2026-06-02', '上海', 'MacBook Pro', 14999, 1),
    ('2026-06-03', '深圳', 'iPhone 15', 7999, 2),
    ('2026-06-03', '广州', 'AirPods Pro', 1899, 3),
    ('2026-06-03', '北京', 'iPhone 15', 7999, 1),
    ('2026-06-03', '深圳', 'MacBook Pro', 14999, 1)
) AS t(order_date, city, product, price, quantity);

老板要的周报格式是:每个城市每款产品的总销售额,横向排列

传统做法是用 CASE WHEN

SELECT
    city,
    SUM(CASE WHEN product = 'iPhone 15' THEN price * quantity ELSE 0 END) AS "iPhone 15",
    SUM(CASE WHEN product = 'MacBook Pro' THEN price * quantity ELSE 0 END) AS "MacBook Pro",
    SUM(CASE WHEN product = 'AirPods Pro' THEN price * quantity ELSE 0 END) AS "AirPods Pro",
    SUM(CASE WHEN product = 'iPad Air' THEN price * quantity ELSE 0 END) AS "iPad Air"
FROM orders
GROUP BY city;

产品多了之后,这条 SQL 会变得极其冗长。而且每次新增产品都要改 SQL——这在自动化报表里是灾难。

用 PIVOT 一行搞定

SELECT * FROM orders
PIVOT ON product
USING SUM(price * quantity)
GROUP BY order_date, city;

结果:

order_date | city   | iPhone 15 | MacBook Pro | AirPods Pro | iPad Air
-----------|--------|-----------|-------------|-------------|----------
2026-06-01 | 上海    | 7999      |             | 3798        |
2026-06-01 | 北京    |           | 14999       |             |
2026-06-02 | 广州    |           |             |             | 4799
2026-06-02 | 北京    |           |             |             |
2026-06-02 | 上海    |           | 14999       |             |
2026-06-03 | 深圳    | 15998     | 14999       |             |
2026-06-03 | 广州    |           |             | 5697        |
2026-06-03 | 北京    | 7999      |             |             |

关键优势:

  1. 动态列 — 不管有多少种产品,PIVOT 自动识别并创建对应列
  2. 简洁 — 一行 SQL 替代几十行 CASE WHEN
  3. 可维护 — 新增产品不需要改任何代码

多聚合函数同时计算

实际业务中,你可能同时需要销售额、订单数和平均客单价:

SELECT * FROM orders
PIVOT ON product
USING SUM(price * quantity) AS total_revenue,
      COUNT(*) AS order_count,
      AVG(price * quantity) AS avg_order_value
GROUP BY order_date, city;

结果会自动生成 total_revenueorder_countavg_order_value 三组列,每种产品对应三列数据。


二、UNPIVOT 实战:把 Excel 宽表变回可分析的长表

场景:客户发来月度 Excel

这是每个数据分析师都经历过的噩梦——市场部同事发来一个 Excel 文件,结构是这样的:

产品1月2月3月4月5月6月
iPhone 15500520480610590650
MacBook Pro200210195230225240
AirPods Pro150016001450170016501800

你想做时间序列分析、趋势预测、同比环比计算——但列太多,没法 GROUP BY。

用 UNPIVOT 一键拍扁

CREATE TABLE monthly_excel AS
SELECT 'iPhone 15' as product, 500 as "1月", 520 as "2月", 480 as "3月", 610 as "4月", 590 as "5月", 650 as "6月"
UNION ALL SELECT 'MacBook Pro', 200, 210, 195, 230, 225, 240
UNION ALL SELECT 'AirPods Pro', 1500, 1600, 1450, 1700, 1650, 1800;

SELECT * FROM monthly_excel
UNPIVOT ON "1月", "2月", "3月", "4月", "5月", "6月"
INTO NAME month VALUE sales;

结果:

product     | month | sales
------------|-------|------
iPhone 15   | 1月   | 500
iPhone 15   | 2月   | 520
iPhone 15   | 3月   | 480
iPhone 15   | 4月   | 610
iPhone 15   | 5月   | 590
iPhone 15   | 6月   | 650
MacBook Pro | 1月   | 200
...

现在可以做任意时间序列分析了:

-- 计算环比增长率
WITH unpivoted AS (
    SELECT * FROM monthly_excel
    UNPIVOT ON "1月", "2月", "3月", "4月", "5月", "6月"
    INTO NAME month VALUE sales
)
SELECT
    product,
    month,
    sales,
    ROUND(sales - LAG(sales) OVER (PARTITION BY product ORDER BY month), 2) AS mom_change
FROM unpivoted
ORDER BY product, month;

三、组合拳:PIVOT + UNPIVOT 构建数据管道

在实际数据产品中,PIVOT 和 UNPIVOT 往往配合使用。

典型数据管道流程

原始数据(窄表) → UNPIVOT(清洗) → 中间处理 → PIVOT(输出报表) → 客户交付

让我们看一个完整的例子:多渠道销售数据整合

假设你有三个渠道的销售数据,格式各不相同:

-- 渠道A:宽表(每月一列)
CREATE TABLE channel_a AS
SELECT '华东区' as region, 100000 as jan, 120000 as feb, 110000 as mar;

-- 渠道B:窄表(交易流水)
CREATE TABLE channel_b AS
SELECT * FROM (VALUES
    ('华东区', '2026-01-15', '电子产品', 50000),
    ('华东区', '2026-02-20', '日用品', 30000),
    ('华东区', '2026-03-10', '电子产品', 45000)
) AS t(region, date, category, amount);

-- 渠道C:混合表(按季度)
CREATE TABLE channel_c AS
SELECT '华东区' as region, 350000 as q1, 420000 as q2;

步骤1:UNPIVOT 渠道A,统一格式

SELECT region, 'jan' as period, jan as amount FROM channel_a
UNION ALL
SELECT region, 'feb', feb FROM channel_a
UNION ALL
SELECT region, 'mar', mar FROM channel_a;

步骤2:UNPIVOT 渠道C,统一到月度

SELECT region,
    'q1_jan' as period, q1/3 as amount  -- 简化:季度均分
FROM channel_c;

步骤3:合并后 PIVOT 输出最终报表

WITH unified AS (
    -- 渠道A展开
    SELECT region, 'jan' as month, jan as amount FROM channel_a
    UNION ALL SELECT region, 'feb', feb FROM channel_a
    UNION ALL SELECT region, 'mar', mar FROM channel_a
    -- 渠道B按月聚合
    SELECT region, STRFTIME(date, '%m') as month, SUM(amount)
    FROM channel_b GROUP BY region, STRFTIME(date, '%m')
    -- 渠道C展开
    SELECT region, 'q1_jan' as month, q1/3 FROM channel_c
    UNION ALL SELECT region, 'q1_feb', feb/3 FROM channel_c
    UNION ALL SELECT region, 'q1_mar', mar/3 FROM channel_c
)
SELECT * FROM unified
PIVOT ON month
USING SUM(amount)
GROUP BY region;

四、Python 集成:在数据产品中无缝衔接

大多数数据产品都有 Python 后端。DuckDB 的 duckdb.sql() 可以直接操作 Python 变量,这让 PIVOT/UNPIVOT 在应用层的使用变得极其方便。

import duckdb
import pandas as pd
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/report/sales-pivot")
def get_sales_pivot(city: str = "上海"):
    """实时销售透视报表"""
    
    # 直接从数据库读取原始交易数据
    conn = duckdb.connect(":memory:")
    conn.execute("INSTALL httpfs; LOAD httpfs;")
    conn.execute("CALL httpfs_download('https://api.example.com/sales', '/tmp/sales.csv')")
    
    # 自动读取 CSV
    conn.execute("CREATE TABLE raw_orders AS SELECT * FROM read_csv_auto('/tmp/sales.csv')")
    
    # PIVOT 生成报表
    result = conn.execute(f"""
        SELECT * FROM raw_orders
        WHERE city = '{city}'
        PIVOT ON product
        USING SUM(price * quantity)
        GROUP BY order_date
    """).fetchdf()
    
    # 转成 JSON 给前端
    return result.to_dict(orient='records')

关键点:

  1. read_csv_auto 自动推断列类型,无需手动定义 schema
  2. duckdb.sql() 可以直接引用 Python 变量,不需要 CREATE TABLE
  3. .fetchdf() 把结果直接转成 DataFrame,方便后续处理
  4. 整个流程在内存中完成,无需写入磁盘

五、对比:PIVOT/UNPIVOT vs 传统方案

方案代码量动态列支持性能可维护性
DuckDB PIVOT1 行 SQL✅ 自动⚡ 向量化⭐⭐⭐⭐⭐
CASE WHENN 行 SQL❌ 手动🐢 逐行计算⭐⭐
Python pivot_table3-5 行✅ 自动🐢 内存中操作⭐⭐⭐
Excel 数据透视表点击操作✅ 自动🐢 大数据量卡顿⭐⭐⭐
SQL 动态拼接复杂✅ 手动🐢 编译开销大

为什么 DuckDB PIVOT 是最优解?

  1. 零代码量 — 一行 SQL 替代 CASE WHEN 的 N 行代码
  2. 向量化执行 — 底层使用 SIMD 指令,比 Python 快 10-100 倍
  3. 流式处理 — 超过内存的数据自动落盘,不会 OOM
  4. 无缝集成 — 可以和 read_csv_auto、httpfs 等功能组合使用

六、变现建议:把行列转换技能变成收入

方案1:数据报表自动化服务(月入 3000-8000)

很多中小企业还在用 Excel 手动做月度报表。你可以:

  1. 用 DuckDB 搭建自动化报表管道
  2. 每周自动从他们的数据库/CSV 提取数据
  3. 用 PIVOT 生成标准报表格式
  4. 通过邮件或 Telegram 自动推送

定价策略:基础版 500 元/月(1 份报表),专业版 2000 元/月(5 份报表 + 自定义字段)。

方案2:数据清洗 SaaS(月入 10000+)

跨境电商卖家经常收到来自不同平台的格式各异的销售数据。你可以做一个在线工具:

  1. 用户上传 CSV/Excel
  2. DuckDB 自动 UNPIVOT 宽表、PIVOT 需要的格式
  3. 输出标准化的 CSV 供他们导入 ERP

技术栈:FastAPI + DuckDB + Streamlit 前端,部署在便宜的 VPS 上(月成本不到 50 元)。

方案3:数据产品模板商店

将常用的 PIVOT/UNPIVOT 模板封装成可复用的 SQL 脚本,在平台上出售:

  • 电商销售周报模板
  • 财务报表模板
  • 社交媒体数据分析模板

每个模板定价 99-299 元,边际成本为零。


总结

PIVOT 和 UNPIVOT 不是炫技的高级功能,而是数据分析师每天都在用的核心技能。

掌握了它们,你就拥有了把任何格式的数据转换成任何格式报表的能力。这种能力,在数据驱动的商业世界里,就是真金白银。

下一步行动:

  1. 打开你的 DuckDB,试试 PIVOT 和 UNPIVOT
  2. 找一个你工作中反复做的数据格式转换,用 PIVOT/UNPIVOT 重写
  3. 考虑把你的报表自动化能力打包成服务

📖 本文的完整代码示例和数据集已整理成教程,在 duckdblab.org 可以找到更多 DuckDB 实战案例和变现方法论。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。