Chapter 02

直接查询文件

无需建表导入,直接 SELECT CSV、Parquet、JSON 文件;glob 批量查询;S3 远程文件直查

2.1 read_csv_auto:自动推断 Schema

DuckDB 最常用的文件读取函数。read_csv_auto 会自动扫描文件前几行,推断每列的数据类型,无需手动指定 Schema。

SQL-- 最简单的写法:直接用文件名作为表名
SELECT * FROM 'orders.csv' LIMIT 10;

-- 等价的显式写法
SELECT * FROM read_csv_auto('orders.csv') LIMIT 10;

-- 查看推断出来的 Schema
DESCRIBE SELECT * FROM 'orders.csv';

-- 也可以用 sniff_csv 预览推断结果
SELECT * FROM sniff_csv('orders.csv');

read_csv 参数详解

当自动推断结果不准确时,可以手动指定参数:

SQLSELECT * FROM read_csv('orders.csv',
    header        = true,          -- 第一行是列名
    delim         = ',',            -- 分隔符(默认逗号)
    quote         = '"',            -- 引号字符
    escape        = '"',            -- 转义字符
    nullstr       = '',             -- 哪些值视为 NULL
    dateformat    = '%Y-%m-%d',    -- 日期格式
    timestampformat = '%Y-%m-%d %H:%M:%S',
    skip          = 1,              -- 跳过前 N 行
    sample_size   = 10000,         -- 用于类型推断的采样行数
    ignore_errors = true,           -- 忽略解析错误的行
    columns = {                       -- 手动指定列类型
        'order_id': 'VARCHAR',
        'amount': 'DECIMAL(10,2)',
        'created_at': 'TIMESTAMP'
    }
);

常用分隔符文件

SQL-- TSV(Tab 分隔)
SELECT * FROM read_csv('data.tsv', delim='\t');

-- 管道分隔
SELECT * FROM read_csv('data.txt', delim='|');

-- 无表头的 CSV(手动指定列名)
SELECT * FROM read_csv('data.csv',
    header=false,
    columns={'id': 'INTEGER', 'name': 'VARCHAR', 'age': 'INTEGER'}
);

2.2 read_parquet:查询 Parquet 文件

Parquet 是列式存储格式,DuckDB 读取 Parquet 效率极高,因为两者都是列式存储,天然兼容。

SQL-- 读取单个 Parquet 文件
SELECT * FROM read_parquet('orders.parquet');
-- 等价简写
SELECT * FROM 'orders.parquet';

-- 查看 Parquet 文件的 Schema 和元数据
SELECT * FROM parquet_schema('orders.parquet');
SELECT * FROM parquet_metadata('orders.parquet');

-- 读取 hive 分区格式(自动识别分区列)
-- 目录结构:data/year=2024/month=01/part-0.parquet
SELECT * FROM read_parquet('data/**/*.parquet', hive_partitioning=true);

-- 只读取特定列(谓词下推,节省 I/O)
SELECT order_id, amount FROM 'orders.parquet' WHERE amount > 1000;

2.3 read_json_auto:查询 JSON 文件

SQL-- 标准 JSON 数组 [ {...}, {...} ]
SELECT * FROM read_json_auto('users.json');

-- NDJSON / JSON Lines 格式(每行一个 JSON 对象)
SELECT * FROM read_json('events.jsonl', format='newline_delimited');

-- 嵌套 JSON:展开数组字段
SELECT
    id,
    name,
    unnest(orders) AS order_item
FROM read_json_auto('users_with_orders.json');

-- 访问嵌套字段
SELECT
    data->>'$.user.name' AS username,
    data->>'$.event.type' AS event_type
FROM read_json_auto('logs.json');

2.4 glob 批量文件查询

glob 模式是 DuckDB 文件查询的超级能力:用通配符一次性查询多个文件,DuckDB 会自动合并结果。

SQL-- 查询同目录所有 CSV 文件
SELECT * FROM 'data/*.csv';

-- 递归查询子目录中的所有 Parquet 文件
SELECT * FROM 'data/**/*.parquet';

-- 查询多个命名文件
SELECT * FROM ['jan.csv', 'feb.csv', 'mar.csv'];

-- 查询特定年月的文件(模式匹配)
SELECT * FROM 'logs/2024-*.csv';

-- 知道哪个文件的数据来自哪里?加 filename 列
SELECT filename, * FROM read_csv('data/*.csv', filename=true);

-- 用 glob() 函数查看会匹配哪些文件
SELECT * FROM glob('data/*.csv');
💡

Hive 分区自动识别 如果文件按 year=2024/month=01/day=01 这样的 Hive 命名规范组织,DuckDB 会自动把目录名解析为列值,可以用 WHERE year=2024 来跳过不需要的分区目录,极大提升性能。

2.5 HTTP/S3 远程文件直接查询

通过 httpfs 扩展(DuckDB 内置,无需额外安装),可以直接查询 HTTP URL 或 S3/GCS/Azure Blob 上的文件。

HTTP 直查

SQL-- 直接查询公开 HTTP URL 上的 Parquet 文件
SELECT * FROM 'https://data.example.com/sales.parquet' LIMIT 100;

-- 查询 GitHub Raw 上的 CSV 文件
SELECT COUNT(*) FROM 'https://raw.githubusercontent.com/user/repo/main/data.csv';

Amazon S3

SQL-- 先加载 httpfs 扩展(现代版本默认已加载)
INSTALL httpfs;
LOAD httpfs;

-- 配置 S3 凭证
SET s3_region='us-east-1';
SET s3_access_key_id='YOUR_KEY';
SET s3_secret_access_key='YOUR_SECRET';

-- 直接查询 S3 上的文件
SELECT * FROM 's3://my-bucket/data/orders.parquet';

-- S3 glob 批量查询
SELECT * FROM 's3://my-bucket/logs/2024/**/*.parquet'
WHERE event_type = 'purchase';

Google Cloud Storage

SQLINSTALL httpfs; LOAD httpfs;

-- GCS(使用 HMAC 密钥)
SET s3_endpoint='storage.googleapis.com';
SET s3_access_key_id='YOUR_HMAC_KEY';
SET s3_secret_access_key='YOUR_HMAC_SECRET';

SELECT * FROM 'gs://my-bucket/data.parquet';

2.6 实战:分析 1GB CSV 日志文件

假设有一个服务器访问日志 CSV 文件,大小约 1GB,包含数百万条记录:

SQL-- 先看一下数据格式
SELECT * FROM sniff_csv('access.log.csv');

-- 统计总行数(DuckDB 扫描 1GB CSV 约 2-5 秒)
SELECT COUNT(*) FROM 'access.log.csv';

-- 按小时统计请求量
SELECT
    date_trunc('hour', strptime(timestamp, '%Y-%m-%d %H:%M:%S')) AS hour,
    COUNT(*) AS requests,
    COUNT(CASE WHEN status_code = 200 THEN 1 END) AS success,
    COUNT(CASE WHEN status_code >= 500 THEN 1 END) AS errors
FROM 'access.log.csv'
GROUP BY hour
ORDER BY hour;

-- Top 10 最慢接口
SELECT
    path,
    COUNT(*) AS call_count,
    AVG(response_ms) AS avg_ms,
    PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_ms) AS p99_ms,
    MAX(response_ms) AS max_ms
FROM 'access.log.csv'
GROUP BY path
ORDER BY p99_ms DESC
LIMIT 10;

-- 分析结束后,把结果保存为 Parquet 供后续使用
COPY (
    SELECT path, COUNT(*) AS calls, AVG(response_ms) AS avg_ms
    FROM 'access.log.csv'
    GROUP BY path
) TO 'api_stats.parquet' (FORMAT PARQUET);

性能参考 在普通笔记本(Apple M1/M2)上,DuckDB 扫描 1GB CSV 通常只需 2-5 秒;如果文件是 Parquet 格式且查询只涉及少数列,速度可以提升 10 倍以上。pandas 读取同样的 1GB CSV 可能需要 30-60 秒。

💡

本章小结 DuckDB 可以直接 SELECT 文件(CSV/Parquet/JSON),无需建表。read_csv_auto 自动推断 Schema,read_parquet 天然高效,read_json_auto 处理嵌套数据。glob 模式支持批量查询多个文件。httpfs 扩展允许直查 HTTP URL 或 S3/GCS 上的文件,是数据工程的利器。