Featured image of post DuckDB COPY TO 导出完全指南:从查询结果到文件,生产级数据管道必备

DuckDB COPY TO 导出完全指南:从查询结果到文件,生产级数据管道必备

DuckDB 的 COPY TO 让你把 SQL 查询结果直接导出为 CSV、Parquet、JSON 等格式。本文覆盖批量导出、大文件分片、远程存储、数据管道集成等实战场景。

DuckDB COPY TO 导出完全指南:从查询结果到文件,生产级数据管道必备

做数据分析时,你肯定遇到过这个需求:把查询结果保存成文件,然后发给老板、导入其他系统、或者作为下一步管道的输入。

传统做法是什么?写 Python 脚本,用 to_csv()to_parquet(),处理编码、分隔符、日期格式……代码冗长,而且容易出错。

DuckDB 的方案更优雅: 一条 SQL 就能导出任何查询结果,支持 CSV、Parquet、JSON、Excel 等多种格式,还能自动分片、压缩、远程写入。


一、基础用法:一句话导出

导出为 CSV

-- 最简单的方式
COPY (SELECT * FROM orders WHERE amount > 1000) 
TO 'orders_expensive.csv';

-- 指定分隔符和编码
COPY (SELECT * FROM sales) 
TO 'sales.csv' (HEADER, DELIMITER ',', ENCODING 'UTF-8');

导出为 Parquet

-- 列式存储,体积缩小 5-10 倍
COPY (SELECT * FROM transactions) 
TO 'transactions.parquet' (FORMAT PARQUET);

-- 带压缩(默认 SNAPPY)
COPY (SELECT * FROM logs) 
TO 'logs.parquet' (FORMAT PARQUET, COMPRESSION 'ZSTD');

导出为 JSON

-- NDJSON 格式(每行一个 JSON 对象)
COPY (SELECT * FROM users) 
TO 'users.json' (FORMAT JSON);

-- 数组格式
COPY (SELECT * FROM products) 
TO 'products.json' (FORMAT JSON, ARRAY true);

二、大文件处理:自动分片

当查询结果超过 1GB 时,单个文件可能不好处理。DuckDB 可以自动分片:

-- 按行数分片(每片最多 100 万行)
COPY (SELECT * FROM massive_table) 
TO 'output/part_*.csv' (HEADER, ROWS_PER_GROUP 1000000);

-- 按文件大小分片(每片约 500MB)
COPY (SELECT * FROM logs) 
TO 'logs_*.parquet' (FORMAT PARQUET, MAX_FILE_SIZE '500MB');

实战:导出 10 亿行日志

-- 假设你有 10 亿行应用日志
COPY (
    SELECT timestamp, level, message, user_id
    FROM app_logs
    WHERE timestamp >= '2026-07-01'
) 
TO 'logs_202607_*.parquet' 
(FORMAT PARQUET, ROWS_PER_GROUP 500000);

-- 输出:logs_202607_001.parquet, logs_202607_002.parquet, ...

三、远程文件导出

DuckDB 可以直接写入远程存储,不需要先保存到本地:

-- AWS S3
COPY (SELECT * FROM metrics) 
TO 's3://bucket/metrics.parquet' 
(FORMAT PARQUET, ACCESS_KEY_ID 'YOUR_KEY', SECRET_ACCESS_KEY 'YOUR_SECRET');

-- Azure Blob Storage
COPY (SELECT * FROM reports) 
TO 'az://container/reports.csv' (HEADER, DELIMITER ',');

-- Google Cloud Storage
COPY (SELECT * FROM analytics) 
TO 'gs://bucket/analytics.json' (FORMAT JSON);

配置凭证(更安全的方式)

-- 设置 S3 凭证一次,后续查询复用
SET s3_access_key_id = 'YOUR_KEY';
SET s3_secret_access_key = 'YOUR_SECRET';
SET s3_region = 'us-east-1';

-- 现在可以直接写入
COPY (SELECT * FROM data) 
TO 's3://bucket/data.parquet' (FORMAT PARQUET);

四、数据管道集成

场景 1:每日 ETL 导出

-- 每天运行,导出前一天的销售数据
COPY (
    SELECT * FROM sales 
    WHERE sale_date = CURRENT_DATE - INTERVAL '1 day'
) 
TO 'daily_sales/' || FORMAT_DATE(CURRENT_DATE - INTERVAL '1 day', 'YYYY-MM-DD') || '.parquet'
(FORMAT PARQUET);

场景 2:多表合并导出

-- 合并多个表后导出
COPY (
    SELECT o.order_id, c.customer_name, p.product_name, o.amount, o.sale_date
    FROM orders o
    JOIN customers c ON o.customer_id = c.id
    JOIN products p ON o.product_id = p.id
    WHERE o.sale_date >= '2026-07-01'
) 
TO 'monthly_report.parquet' (FORMAT PARQUET);

场景 3:API 数据源 → DuckDB → 导出

import duckdb
import requests

# 1. 获取 API 数据
response = requests.get('https://api.example.com/sales')
data = response.json()

# 2. 加载到 DuckDB
con = duckdb.connect()
con.execute("CREATE TABLE api_data AS SELECT * FROM ?::JSON", [data])

# 3. 清洗并导出
con.execute("""
    COPY (
        SELECT * FROM api_data 
        WHERE amount IS NOT NULL AND amount > 0
    ) TO 'cleaned_sales.parquet' (FORMAT PARQUET)
""")

con.close()

五、性能优化技巧

1. 使用 Parquet 而不是 CSV

格式压缩比读取速度适用场景
CSV1x人类可读、小文件
Parquet5-10x分析、大数据
JSON2-3xAPI 交换
-- 推荐:日常分析用 Parquet
COPY (SELECT * FROM large_table) 
TO 'output.parquet' (FORMAT PARQUET);

-- 仅当需要人类可读时用 CSV
COPY (SELECT * FROM summary) 
TO 'summary.csv' (HEADER, DELIMITER ',');

2. 控制并行度

-- 限制导出线程数(避免占用太多 CPU)
SET parallel_export_threads = 2;

COPY (SELECT * FROM data) 
TO 'output.csv';

3. 流式写入

对于超大结果集,使用流式写入避免内存爆炸:

-- 自动分批写入,不一次性加载全部结果
COPY (
    SELECT * FROM huge_table
    ORDER BY id
) 
TO 'stream_output.parquet' 
(FORMAT PARQUET, ROWS_PER_GROUP 100000);

六、常见陷阱与解决方案

陷阱 1:CSV 编码问题

-- ❌ 中文乱码
COPY (SELECT * FROM chinese_data) TO 'output.csv';

-- ✅ 指定 UTF-8
COPY (SELECT * FROM chinese_data) 
TO 'output.csv' (HEADER, ENCODING 'UTF-8');

陷阱 2:日期格式不一致

-- ❌ 默认格式可能不符合预期
COPY (SELECT * FROM events) TO 'events.csv';

-- ✅ 显式格式化日期
COPY (
    SELECT event_date::DATE AS date, event_time::TIME AS time, *
    FROM events
) TO 'events.csv' (HEADER);

陷阱 3:空值处理

-- ❌ NULL 在 CSV 中显示为空,难以区分
COPY (SELECT * FROM data) TO 'output.csv';

-- ✅ 用 COALESCE 替换 NULL
COPY (
    SELECT COALESCE(name, 'UNKNOWN') AS name, COALESCE(amount, 0) AS amount
    FROM data
) TO 'output.csv' (HEADER);

七、生产级模板

这是一个可直接复用的导出函数:

import duckdb
from pathlib import Path

class DataExporter:
    def __init__(self, db_path: str):
        self.con = duckdb.connect(db_path)
    
    def export_query(self, query: str, output_path: str, 
                     fmt: str = 'parquet', **kwargs):
        """通用导出函数"""
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        
        self.con.execute(f"""
            COPY ({query}) 
            TO '{output_path}'
            (FORMAT {fmt.upper()}, {', '.join(f'{k}={v}' for k,v in kwargs.items())})
        """)
    
    def export_daily(self, table: str, date: str):
        """导出指定日期的数据"""
        query = f"SELECT * FROM {table} WHERE date = '{date}'"
        output = f"exports/{date}.parquet"
        self.export_query(query, output)

# 使用示例
exporter = DataExporter('analytics.duckdb')
exporter.export_daily('sales', '2026-07-21')
exporter.export_daily('users', '2026-07-21')

八、效果量化

指标Python pandasDuckDB COPY TO
代码行数10-20 行1 行 SQL
处理 1GB 数据~30 秒~5 秒
内存占用全量加载流式写入
并行导出需手动实现内置支持
远程写入需额外库原生支持

总结

DuckDB 的 COPY TO 让你能够:

  • 一行 SQL 导出任意查询结果
  • 支持 CSV、Parquet、JSON、Excel 等多种格式
  • 自动分片大文件,避免内存爆炸
  • 直接写入 S3、Azure、GCS 等远程存储
  • 集成到数据管道,无需 Python 预处理

记住:导入和导出同样重要。你的数据管道不应该只进不出——用 COPY TO 让查询结果真正流动起来。


📌 下一步行动:下次需要导出数据时,试试 COPY (SELECT ...) TO 'file.parquet',看看比 Python 快多少。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

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

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

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