4. 数据导入与导出

在金融研究场景中,数据往往来自多个渠道:交易所行情文件、Wind/同花顺导出表、券商柜台 CSV、Python 爬虫抓取的 JSON、Excel 维护的财务底稿。DuckDB 的价值在于,它既能像 SQLite 一样本地零配置运行,又能像分析型数据库一样直接对文件发起 SQL 查询。理解各格式的读写机制,是搭建本地研究流水线的第一步。

核心原理:DuckDB 如何“看见”文件

DuckDB 提供两类文件访问方式。第一类是扫描函数,如 read_csv_auto、read_parquet、read_json_auto,它们把文件当作外部表直接查询,支持谓词下推与列裁剪,不产生中间副本。第二类是导入导出语句,如 COPY ... TO 与 COPY ... FROM,用于把结果落盘或把文件灌入本地表。前者适合探索与即席分析,后者适合固化数据集与交付结果。

需要特别注意的是,DuckDB 对 Parquet 的支持是“原生列式”的:查询 Parquet 时只读取所需列与行组,I/O 成本远低于 CSV。因此,在金融数据量较大时,推荐把 CSV 先转成 Parquet 再反复使用。

DuckDB 文件读写数据流 CSV 行情文件 read_csv_auto Parquet 列存 read_parquet JSON 接口数据 read_json_auto Excel 底稿 st_read / 转换 DuckDB 本地分析引擎 SQL 查询 / 聚合 谓词下推 / 列裁剪 结果表 / 视图 COPY TO 导出 Parquet 交付 列存压缩 CSV 报表 通用交换 Excel 输出 需外部库

方法流程:从文件到可分析表

一个稳健的导入流程通常包含四步:探测结构、声明类型、批量加载、校验结果。下面以 A 股日行情 CSV 为例。

-- 1. 自动探测 CSV 结构
DESCRIBE SELECT * FROM read_csv_auto('data/ashare_daily.csv');

-- 2. 显式声明类型,避免日期被识别为字符串
CREATE TABLE ashare_daily AS
SELECT * FROM read_csv_auto(
  'data/ashare_daily.csv',
  header = true,
  columns = {
    'trade_date': 'DATE',
    'ts_code': 'VARCHAR',
    'open': 'DOUBLE',
    'high': 'DOUBLE',
    'low': 'DOUBLE',
    'close': 'DOUBLE',
    'vol': 'BIGINT',
    'amount': 'DOUBLE'
  }
);

-- 3. 批量加载多个文件(通配符)
CREATE TABLE ashare_all AS
SELECT * FROM read_parquet('data/parquet/*.parquet');

-- 4. 校验行数与日期范围
SELECT COUNT(*) AS rows, MIN(trade_date), MAX(trade_date) FROM ashare_daily;

导出同样简单。若要把研究结果交付给同事,Parquet 是首选;若要给业务方看,CSV 更通用。

-- 导出为 Parquet(推荐,列存压缩)
COPY (SELECT * FROM ashare_daily WHERE trade_date >= '2024-01-01')
TO 'output/ashare_2024.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);

-- 导出为 CSV
COPY (SELECT ts_code, trade_date, close FROM ashare_daily)
TO 'output/close_panel.csv' (HEADER, DELIMITER ',');
Excel 的处理策略:DuckDB 原生不直接读 .xlsx。推荐做法是先用 Python 的 pandas/openpyxl 把 Excel 转为 CSV 或 Parquet,再交给 DuckDB 查询;或者安装 spatial 扩展后使用 st_read 读取部分表格格式。对于财务底稿,建议固定“Excel→Parquet”的转换脚本,保证每次研究可复现。

金融数据源对接场景

常见金融数据源与 DuckDB 的对接方式可归纳如下:

数据源典型格式推荐接入方式注意事项
交易所日行情CSV / Parquetread_csv_auto / read_parquet日期与代码类型需显式声明
Wind / 同花顺导出Excel / CSV先转 Parquet 再查询表头多行、合并单元格需预处理
券商柜台接口JSON / CSVread_json_auto嵌套字段用 -> 提取
Python 爬虫结果JSON Linesread_json_auto(..., format='newline_delimited')逐行解析,内存友好
财务底稿Excelpandas 转换后落 Parquet保留原始 sheet 名与版本

对于 JSON 嵌套结构,DuckDB 支持用 ->> 提取标量、用 json_extract 提取对象,再配合 UNNEST 展开数组。例如解析某接口返回的行情数组:

SELECT
  json_extract_string(item, '$.code') AS code,
  json_extract_string(item, '$.date') AS trade_date,
  CAST(json_extract(item, '$.close') AS DOUBLE) AS close
FROM (
  SELECT UNNEST(json_extract(data, '$.items')) AS item
  FROM read_json_auto('data/quotes.json')
) t;

误区与复盘

  • 误区一:全部用 read_csv_auto 不声明类型。金融数据中的股票代码常被识别为整数,导致前导零丢失;日期列可能被识别为字符串,后续比较出错。务必用 columns 参数显式声明。
  • 误区二:反复读取大 CSV。CSV 无压缩、无列裁剪,每次查询都要全量解析。正确做法是首次导入后立即 COPY TO ... (FORMAT PARQUET),后续只查 Parquet。
  • 误区三:忽略编码与分隔符。国内数据常为 GBK 编码或使用制表符分隔,需在参数中指定 encoding='gbk' 与 delim='\t'。
  • 误区四:导出时覆盖原始数据。建议输出目录与原始数据目录分离,文件名带日期或版本号,避免不可逆覆盖。
批量加载提醒:使用通配符加载大量 Parquet 文件时,DuckDB 会并行读取,但文件数量过多(如数万个)会带来元数据开销。建议按年份或品种分目录,并在查询中用 WHERE 过滤分区列,充分利用分区裁剪。

掌握这些读写能力后,你就能把散落在 CSV、Parquet、JSON、Excel 中的金融数据统一纳入 DuckDB,用一条 SQL 完成清洗、聚合与导出,为后续的因子计算与回测打下可复现的数据基础。