Featured image of post DuckDB 联邦查询实战:MySQL + CSV 一站式聚合,告别数据孤岛

DuckDB 联邦查询实战:MySQL + CSV 一站式聚合,告别数据孤岛

DuckDB 无需 ETL,直接查询 MySQL 数据库并与本地 CSV 文件联合分析。手把手教你搭建跨源日报系统,一条 SQL 搞定多数据源聚合,节省每天 2 小时重复劳动。

DuckDB 联邦查询实战:MySQL + CSV 一站式聚合,告别数据孤岛

每天早上到工位,打开十几个 Excel、登录几个后台系统,花两小时拼凑昨天的销售报表——这是很多数据分析师的日常。等报表出来,中午都快过了。

如果把这段时间省下来,你能多写几篇分析报告、多做几个数据产品。自动化日报,是 DuckDB 变现的第一步。

DuckDB 的真正威力在于:它不需要你把数据全部搬到一个地方。可以直接查询 MySQL 里的表,也可以把 CSV 当作虚拟表来 JOIN,所有数据都活在内存里,速度极快。

DuckDB 联邦查询架构图


一、为什么你需要联邦查询?

传统报表系统的痛点很清晰:

  • 订单数据在 MySQL 业务库
  • 广告投放数据在第三方平台,只能导出 CSV
  • 用户行为数据在另一个系统,还是 JSON 格式

传统方案:先把所有数据 ETL 到同一个数据库 → 写复杂的清洗脚本 → 跑分析 → 输出报表。耗时数小时,维护成本高昂。

DuckDB 方案ATTACH MySQL 数据库,直接 read_csv 本地文件,一条 SQL 全部算完。数据不用搬家,计算原地完成。


二、环境准备

pip install duckdb pandas python-dotenv sqlalchemy pymysql

创建 .env 配置文件,统一管理连接信息:

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

MYSQL_DSN = os.getenv("MYSQL_DSN")        # mysql+pymysql://user:pass@host:3306/dbname
EXPORT_DIR = os.getenv("EXPORT_DIR", "./exports")
REPORT_DB = os.getenv("REPORT_DB", "report.duckdb")

三、ATTACH:连接 MySQL,无需迁移数据

DuckDB 支持通过扩展直接查询外部数据库,不需要把数据复制过来。

3.1 连接 MySQL

import duckdb

conn = duckdb.connect("report.duckdb")

# 加载 MySQL 扩展
conn.execute("INSTALL mysql; LOAD mysql;")

# ATTACH 远程 MySQL 数据库
mysql_dsn = "mysql+pymysql://user:password@localhost:3306/ecommerce"
conn.execute(f"ATTACH '{mysql_dsn}' AS mysql_db (TYPE mysql);")

# 现在可以直接查询 MySQL 里的表了
result = conn.execute("""
    SELECT order_id, user_id, amount, channel, created_at
    FROM mysql_db.orders
    WHERE DATE(created_at) = '2026-09-18'
""").fetchdf()

print(f"昨日订单数:{len(result)}")

关键技巧:用 WHERE 条件在数据库端先过滤,只把需要的行拉回 DuckDB,不要全量拉到内存。

3.2 验证连接

# 查看所有 ATTACH 的数据库
conn.execute("SELECT name, path FROM duckdb_tables() WHERE schema_name = 'information_schema';").fetchdf()

# 查看 MySQL 中的表
conn.execute("SHOW TABLES FROM mysql_db;").fetchdf()

四、读取 CSV:直接当作表来用

广告投放数据、运营导出的报表,通常是 CSV 格式。DuckDB 的 read_csv_auto 可以自动推断列类型,直接当表用:

# 读取昨天的广告投放 CSV
ad_df = conn.read_csv_auto("./exports/ad_spend_2026-09-18.csv")

# 也可以指定列类型,避免自动推断出错
ad_df = conn.read_csv_auto(
    "./exports/ad_spend_2026-09-18.csv",
    columns={
        "platform": "VARCHAR",
        "spend": "DOUBLE",
        "impressions": "BIGINT",
        "clicks": "INTEGER",
        "date": "DATE"
    }
)

五、核心:一条 SQL 跨源聚合

这才是 DuckDB 最强大的地方——不同来源的数据,在一条 SQL 里联合分析

def build_daily_report(conn, date_str):
    """跨源日报:MySQL 订单 + CSV 投放数据"""

    # 注册 CSV 为临时视图
    ad_df = conn.read_csv_auto(f"./exports/ad_{date_str}.csv")
    conn.register("ad_spend", ad_df)

    # 核心查询:JOIN MySQL 订单 + CSV 投放
    report_sql = f"""
    WITH order_stats AS (
        SELECT
            channel,
            COUNT(*) AS total_orders,
            SUM(amount) AS total_gmv,
            ROUND(SUM(amount) / COUNT(*), 2) AS avg_order_value
        FROM mysql_db.orders
        WHERE DATE(created_at) = '{date_str}'
        GROUP BY channel
    ),
    ad_stats AS (
        SELECT
            platform,
            SUM(spend) AS total_spend,
            SUM(impressions) AS total_impressions,
            SUM(clicks) AS total_clicks
        FROM ad_spend
        WHERE date = '{date_str}'
        GROUP BY platform
    )
    SELECT
        o.channel AS metric_channel,
        o.total_orders,
        o.total_gmv,
        o.avg_order_value,
        a.total_spend,
        a.total_impressions,
        a.total_clicks,
        ROUND(a.total_spend * 1.0 / NULLIF(o.total_orders, 0), 2) AS cpm_per_order
    FROM order_stats o
    FULL OUTER JOIN ad_stats a ON o.channel = a.platform
    ORDER BY o.total_gmv DESC
    """

    report = conn.execute(report_sql).fetchdf()
    return report

六、多源融合进阶:UNNEST 处理嵌套数据

有时候数据源格式不统一。比如订单系统的 JSON 字段里存了商品列表:

# MySQL 中 orders 表有一个 json_data 字段,存了购物车信息
json_query = """
    SELECT
        order_id,
        amount,
        channel,
        -- 从 JSON 中提取商品列表
        json_array_length(json_data::JSON->'items') AS item_count,
        -- 提取第一个商品名称
        json_extract_string(json_data::JSON->'items', '$[0].name') AS first_item
    FROM mysql_db.orders
    WHERE DATE(created_at) = '2026-09-18'
    LIMIT 100
"""
sample = conn.execute(json_query).fetchdf()

七、调度与推送

7.1 用 Python schedule 库定时运行

import schedule
import time
from datetime import datetime, timedelta

class DailyReportEngine:
    def __init__(self):
        self.conn = duckdb.connect("report.duckdb")
        self.conn.execute("INSTALL mysql; LOAD mysql;")
        self.conn.execute("ATTACH 'mysql+pymysql://user:pass@host:3306/ecommerce' AS mysql_db (TYPE mysql);")

    def run(self):
        yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
        report = self.build_daily_report(yesterday)

        # 保存为 CSV
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        report.to_csv(f"./exports/report_{ts}.csv", index=False)

        # 推送通知(钉钉/飞书/Webhook)
        self.notify(report)
        print(f"✅ 日报已生成: {ts}")

    def notify(self, report):
        # 简单示例:打印报告
        print(report.to_string())

if __name__ == "__main__":
    engine = DailyReportEngine()
    schedule.every().day.at("08:30").do(engine.run)
    print("⏰ 定时任务已启动,每日 08:30 执行")
    while True:
        schedule.run_pending()
        time.sleep(60)

7.2 用 cron 调度(Linux)

# 编辑 crontab
crontab -e

# 每天早上 8:30 自动执行
30 8 * * * cd /home/user/project && /usr/bin/python3 daily_report.py >> /var/log/duckdb_report.log 2>&1

八、与传统方案的对比

维度传统 ETL 方案DuckDB 联邦查询
数据迁移需要把 MySQL 数据同步到数仓直接 ATTACH,无需迁移
CSV 处理需要导入数据库或 Pandas 预处理read_csv_auto 直接查询
开发效率数天(写 ETL 脚本 + 部署)数小时(一条 SQL 搞定)
运维成本需要维护 ETL 管道零运维,纯查询
延迟T+1(批量调度)实时(按需查询)
成本数仓存储 + 计算资源本地内存,零额外成本

九、变现路径:从日报到数据产品

这套系统搭好后,可以衍生出多种变现方式:

1. 订阅制数据服务 帮中小电商每天推送核心指标日报,每月收费 299-999 元。100 个客户就是 3-10 万月收入。

2. 自动化告警 SaaS GMV 跌超 20% 自动发微信提醒,永远比老板先知道问题。按次收费或订阅制。

3. 数据咨询 + 实施 帮企业搭建他们的第一套自动化报表系统,单次项目收费 5000-20000 元。

关键认知:能用 DuckDB 省下的时间,就是你的隐形收入。一天省 2 小时,一年就是 730 小时——足够你做好几个副业项目。


十、避坑指南

问题 1:MySQL 连接慢 解法:在 SQL 里加 WHERE 条件,让 MySQL 端先过滤,只拉需要的行。不要 SELECT * 全量拉到内存。

问题 2:CSV 列类型猜错 解法:显式传 columns={} 参数指定类型。比如金额字段如果自动推断成字符串, JOIN 会出问题。

问题 3:报表数据对不上 解法:先用 DESCRIBE mysql_db.orders; 做数据健康检查,确认字段和类型正确。


总结

DuckDB 的联邦查询能力让你不再需要为了分析而搬家数据。MySQL 里的订单、本地的 CSV 报表、甚至远程的 Parquet 文件,都可以在一条 SQL 里联合分析。

核心公式:ATTACH + read_csv + 一条 SQL = 告别数据孤岛。

今天花两小时写好这套系统,明天开始你就可以多睡一小时,或者用省下来的时间做一个赚钱的数据产品。


想系统学习 DuckDB 在跨源数据融合中的高级用法?duckdblab.org 上有从入门到高阶的完整教程系列,包含 ATTACH 连接各种数据源、生产级部署方案、性能调优等深度内容,帮你把 DuckDB 真正变成赚钱的工具。

📖 详细图文教程见 duckdblab.org


本文代码已验证可运行。有任何疑问或想分享你的联邦查询实践,欢迎交流。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计