duckdb使用详解
1. 下载安装
✅✅ 【方式 1:安装 DuckDB 命令行(CLI)——最推荐】
✅ 步骤 1:进入下载页面
官方 CLI 下载地址:
👉 https://duckdb.org/docs/installation
找到 Windows → Command Line Interface (CLI)
会看到类似文件:
duckdb_cli-windows-amd64.zip
也可以直接从 GitHub 下载最新版:
👉 https://github.com/duckdb/duckdb/releases
✅ 步骤 2:解压到任意目录,例如:
C:\Tools\duckdb\
解压后会有一个可执行文件:
duckdb.exe
✅ 步骤 3(可选):加入系统 PATH
将 C:\Tools\duckdb\ 加入 PATH:
- 右键 “此电脑” → 属性
- 高级系统设置 → 环境变量
- 编辑 Path → 添加
C:\Tools\duckdb\
✅ 加入 PATH 以后可以直接在命令行使用:
duckdb
就进入 DuckDB CLI:
v1.x.x DuckDB CLI Enter ".help" for usage hints.

✔ 你可以执行 SQL
✔ 不需要启动数据库
✔ 不需要服务端
✅ ✅ 【方式 2:在 Docker 中使用】
docker run -it --rm ghcr.io/duckdb/duckdb:latest
适合容器环境。
✅ 如何判断安装成功?
在命令行输入:
duckdb
就进入 DuckDB CLI:
v1.x.x DuckDB CLI Enter ".help" for usage hints.
2. 知识获取
✅ 1. 官方网站 和 文档
- ✅ 官方网站:
https://duckdb.org/
- ✅ 官方文档:
https://duckdb.org/docs/
- ✅ GitHub (开源仓库):
https://github.com/duckdb/duckdb
- ✅ Release 下载(Windows / macOS / Linux):
https://duckdb.org/docs/installation/
✅ 官方博客(技术文章、性能对比、优化原理):
https://duckdb.org/news/

✅ 2. DuckDB 的社区与生态
-
开发团队主要来自奥地利与荷兰的数据库研究人员
-
商业公司:DuckDB Labs 提供专业支持
-
GitHub Star 数非常高,增长速度快
-
已被大量数据科学、BI、数据工程、机器学习社区采用
-
集成度很高:可以在 Python、R、Node.js、Go、C++、Rust、Java 中使用
-
PowerBI、Tableau、Superset 等 BI 工具开始支持
-
云数据平台(Snowflake、Databricks)也在研究支持 DuckDB 格式的查询
3. 基本功能能力(不结合数据)
✅ ✅ (1)无需部署,开箱即用
下载一个 duckdb 可执行文件就能运行:
duckdb SELECT 1;
不需要服务、不需要端口、不需要 root 权限。

✅ ✅ (2)支持完整 SQL 查询
-
SELECT / JOIN / GROUP BY / HAVING
-
CTE、窗口函数、聚合函数
-
子查询、视图、索引
它是完整 SQL 语义,而不是简化版。
✅ ✅ (3)列式存储 + 向量化执行引擎
-
扫描速度快
-
聚合、排序、分组性能优秀
-
和大数据分析引擎(ClickHouse、Spark)实现方式类似
-
单机即可处理数百万、上亿行数据
✅ ✅ (4)可以直接读取多种数据源(无需导入)
| 文件格式 | 支持? |
|---|---|
| CSV | ✅ |
| Parquet | ✅ |
| JSON | ✅ |
| Excel(需扩展) | ✅ |
| Arrow | ✅ |
| SQLite 表 | ✅ |
| PostgreSQL 表 | ✅ |
不需要 “导入到数据库” 再查询,直接查询文件本身。
✅ (5)一句话总结
DuckDB = 专门为本地数据分析准备的小型列式数据仓库
无需服务端、支持 SQL、快、支持 CSV/Parquet/Excel、开源免费。
4. 结合实际数据我们来做几个样例
4.1. 使用python生成数据
4.1.1. 生成数据代码
conda install xlsxwriter
# Generate a realistic random sales dataset for DuckDB demos
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import random
import os# ====== 配置文件路径 ======
xlsx_path = r"F:\Data\sales_sample.xlsx"
csv_path = r"F:\Data\sales_sample.csv"# 如果文件夹不存在,自动创建
os.makedirs(os.path.dirname(xlsx_path), exist_ok=True)np.random.seed(42)
random.seed(42)# Config
N = 10000 # number of order rows
start_date = datetime(2024, 1, 1)
end_date = datetime(2025, 11, 1)
days_range = (end_date - start_date).daysregions = ["华东","华南","华北","华中","西南","西北","东北"]
channels = ["Web","App","门店","电销","经销商"]
payment_methods = ["支付宝","微信支付","银联","现金","刷卡"]
categories = {"手机数码":["手机","平板","耳机","智能手表","相机"],"家居家电":["空调","洗衣机","电冰箱","扫地机器人","净水器"],"生鲜食品":["牛奶","鸡蛋","苹果","三文鱼","牛排"],"服装鞋帽":["T恤","牛仔裤","运动鞋","外套","连衣裙"],"办公用品":["打印机","墨盒","A4纸","订书机","显示器"]
}
category_names = list(categories.keys())# Make a tiny customer dimension
cust_n = 500
customer_ids = [f"C{str(i).zfill(5)}" for i in range(1, cust_n+1)]
cities = ["北京","上海","广州","深圳","杭州","成都","武汉","西安","南京","苏州","长沙","青岛"]
vip_levels = ["普通","银卡","金卡","白金"]customers = pd.DataFrame({"customer_id": customer_ids,"name": [f"用户{np.random.randint(100000,999999)}" for _ in range(cust_n)],"city": np.random.choice(cities, size=cust_n, replace=True),"vip_level": np.random.choice(vip_levels, size=cust_n, replace=True, p=[0.55,0.25,0.15,0.05]),"signup_date": [start_date + timedelta(days=int(np.random.rand()*days_range)) for _ in range(cust_n)],
})# Generate orders
order_ids = [f"O{str(i).zfill(7)}" for i in range(1, N+1)]
order_dates = [start_date + timedelta(days=int(np.random.rand()*days_range)) for _ in range(N)]
customer_pick = np.random.choice(customer_ids, size=N, replace=True)
region_pick = np.random.choice(regions, size=N, replace=True)
channel_pick = np.random.choice(channels, size=N, replace=True)cat_pick = np.random.choice(category_names, size=N, replace=True)
prod_pick = [np.random.choice(categories[c]) for c in cat_pick]# Unit prices
base_price = {"手机数码": (300, 8000),"家居家电": (500, 10000),"生鲜食品": (5, 200),"服装鞋帽": (30, 1200),"办公用品": (10, 5000)
}
unit_prices = [round(np.random.lognormal(mean=np.log((base_price[c][0]+base_price[c][1])/2), sigma=0.6), 2)for c in cat_pick
]# Clamp
for i, c in enumerate(cat_pick):mn, mx = base_price[c]unit_prices[i] = float(np.clip(unit_prices[i], mn, mx))qty = np.random.randint(1, 6, size=N)
bulk_idx = np.random.choice(np.arange(N), size=int(0.02*N), replace=False)
qty[bulk_idx] = np.random.randint(6, 50, size=bulk_idx.size)discount = np.round(np.random.choice([0,0.05,0.1,0.15,0.2], size=N, p=[0.55,0.2,0.15,0.07,0.03]), 2)
payment_pick = np.random.choice(payment_methods, size=N, replace=True)# Missing values
nan_indices = np.random.choice(np.arange(N), size=int(0.015*N), replace=False)
payment_pick[nan_indices] = None
disc_nan_idx = np.random.choice(np.arange(N), size=int(0.01*N), replace=False)
discount[disc_nan_idx] = np.nanamount = np.round((np.array(unit_prices) * qty) * (1 - np.nan_to_num(discount, nan=0.0)), 2)orders = pd.DataFrame({"order_id": order_ids,"order_date": order_dates,"customer_id": customer_pick,"region": region_pick,"channel": channel_pick,"category": cat_pick,"product": prod_pick,"unit_price": unit_prices,"quantity": qty,"discount": discount,"amount": amount,"payment_method": payment_pick
})# Add anomalies
an_rows = np.random.choice(np.arange(N), size=10, replace=False)
orders.loc[an_rows[:5], "quantity"] = 0
orders.loc[an_rows[5:], "quantity"] = -np.random.randint(1, 5, size=5)outlier_idx = np.random.choice(np.arange(N), size=3, replace=False)
orders.loc[outlier_idx, "unit_price"] *= 50dup_idx = np.random.choice(np.arange(N), size=5, replace=False)
dups = orders.iloc[dup_idx].copy()
orders_with_dups = pd.concat([orders, dups], ignore_index=True)# Save to XLSX and CSV
with pd.ExcelWriter(xlsx_path, engine="xlsxwriter") as writer:orders_with_dups.to_excel(writer, sheet_name="orders", index=False)customers.to_excel(writer, sheet_name="customers", index=False)orders_with_dups.to_csv(csv_path, index=False, encoding="utf-8-sig")print("✅ 已生成:")
print("Excel:", xlsx_path)
print("CSV:", csv_path)
4.1.2. 生成数据信息介绍
4.1.2.1.✅ 数据集说明:sales_sample(示例销售数据集)
该脚本自动生成一个模拟的真实销售业务数据集,包含两张表:
✅ 订单事实表:orders(约 10,005 行,其中含 5 条重复)
✅ 客户维表:customers(500 名客户)
数据内容涵盖地区、渠道、商品分类、支付方式、折扣、金额、多种异常数据等,非常适合:
-
DuckDB / SQL / 数据仓库教程
-
数据清洗与异常检测案例
-
数据分析、报表、BI 测试
-
ETL 转换、Parquet 导出、数据湖演示
-
Pandas 与 DuckDB 性能对比案例
✅ 一、订单表:orders(sheet: orders)
| 字段名 | 类型 | 说明 |
|---|---|---|
order_id | 字符串 (如 O0000001) | 每条订单唯一编号,格式化为 7 位递增编号 |
order_date | 日期 | 订单发生时间,介于 2024-01-01 ~ 2025-11-01 随机分布 |
customer_id | 字符串 (如 C00001) | 购买用户,与 customers 表关联 |
region | 枚举 | 华东、华南、华北、华中、西南、西北、东北 |
channel | 枚举 | Web、App、门店、电销、经销商 |
category | 枚举 | 手机数码、家居家电、生鲜食品、服装鞋帽、办公用品 |
product | 商品名称 | 如:手机、显示器、打印机、耳机、苹果、空调… |
unit_price | 单价 | 按分类定价区间内随机生成,含价格异常值 |
quantity | 数量 | 正常范围 1~5,但有异常数量(0 或负数) |
discount | 折扣率 | 0、5%、10%、15%、20%,部分为空缺 |
amount | 实付金额 | (单价 × 数量 × (1-折扣)),含异常数据 |
payment_method | 支付方式 | 支付宝、微信支付、银联、现金、刷卡,部分缺失值 |
✅ 订单量与数据特点
- 总订单数:10,000 行 + 5 条重复 = 10,005 行
- 跨 22 个月随机分布,具备季节性分析可能
- 支持区域 / 渠道 / 品类 / 商品粒度分析
- 金额范围真实,适合做交易额 / GMV 分析
✅ 二、客户表:customers(sheet: customers)
| 字段名 | 说明 |
|---|---|
customer_id | 主键,C00001~C00500 |
name | 随机中文用户名,如“用户123456” |
city | 一线/新一线主要城市 |
vip_level | 普通、银卡、金卡、白金(含概率分布) |
signup_date | 注册时间,分布同订单时间段 |
4.2. 使用duckdb统计数据
DuckDB 支持完整 SQL,可轻松针对订单数据进行基础经营分析:
- 总订单数(Orders)
- 总销售额(GMV / Revenue)
- 平均客单价(AOV / Avg Order Value)
- 总销量、交易笔数、毛利模拟
- 用户数量、下单人数
4.2.1. 加载数据
4.2.1.1 加载csv
PS C:\Users\caimingyang> duckdb
DuckDB v1.4.1 (Andium) b390a7c376
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.D SELECT * FROM read_csv_auto('F:/Data/sales_sample.csv') LIMIT 10;
┌──────────┬────────────┬─────────────┬─────────┬─────────┬───┬──────────┬──────────┬──────────┬────────────────┐
│ order_id │ order_date │ customer_id │ region │ channel │ … │ quantity │ discount │ amount │ payment_method │
│ varchar │ date │ varchar │ varchar │ varchar │ │ int64 │ double │ double │ varchar │
├──────────┼────────────┼─────────────┼─────────┼─────────┼───┼──────────┼──────────┼──────────┼────────────────┤
│ O0000001 │ 2024-04-24 │ C00466 │ 华南 │ App │ … │ 3 │ 0.1 │ 12411.95 │ 现金 │
│ O0000002 │ 2024-10-01 │ C00288 │ 华南 │ 门店 │ … │ 5 │ 0.1 │ 5310.04 │ 支付宝 │
│ O0000003 │ 2024-10-10 │ C00353 │ 东北 │ 门店 │ … │ 4 │ 0.05 │ 18798.87 │ 支付宝 │
│ O0000004 │ 2025-07-04 │ C00164 │ 华北 │ 电销 │ … │ 1 │ 0.0 │ 105.43 │ 刷卡 │
│ O0000005 │ 2024-01-01 │ C00412 │ 西南 │ App │ … │ 2 │ 0.05 │ 15200.0 │ 银联 │
│ O0000006 │ 2024-07-20 │ C00400 │ 华北 │ 电销 │ … │ 4 │ 0.0 │ 392.76 │ 现金 │
│ O0000007 │ 2025-10-19 │ C00016 │ 华中 │ App │ … │ 1 │ 0.1 │ 174.32 │ 支付宝 │
│ O0000008 │ 2024-11-14 │ C00018 │ 东北 │ 门店 │ … │ 4 │ 0.0 │ 10442.36 │ 现金 │
│ O0000009 │ 2024-09-13 │ C00136 │ 西北 │ 电销 │ … │ 4 │ 0.1 │ 18960.01 │ 刷卡 │
│ O0000010 │ 2024-03-21 │ C00490 │ 东北 │ App │ … │ 2 │ 0.05 │ 3060.25 │ 刷卡 │
├──────────┴────────────┴─────────────┴─────────┴─────────┴───┴──────────┴──────────┴──────────┴────────────────┤
│ 10 rows 12 columns (9 shown) │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
D
4.2.1.2. 加载excel
直接运行加载会报错
D SELECT * FROM read_csv_auto('F:/Data/sales_sample.xlsx') LIMIT 10;
Invalid Input Error:
Error when sniffing file "F:/Data/sales_sample.xlsx".
It was not possible to automatically detect the CSV parsing dialect
The search space used was:
Delimiter Candidates: ',', '|', ';', ' '
Quote/Escape Candidates: ['(no quote)','(no escape)'],['"','(no escape)'],['"','"'],['"','''],['"','\'],[''','(no escape)'],[''','''],[''','"'],[''','\']
Comment Candidates: '\0', '#'
Encoding: utf-8
Possible fixes:
* Disable the parser's strict mode (strict_mode=false) to allow reading rows that do not comply with the CSV standard.
* Make sure you are using the correct file encoding. If not, set it (e.g., encoding = 'utf-16').
* Set delimiter (e.g., delim=',')
* Set quote (e.g., quote='"')
* Set escape (e.g., escape='"')
* Set comment (e.g., comment='#')
* Set skip (skip=${n}) to skip ${n} lines at the top of the file
* Enable ignore errors (ignore_errors=true) to ignore potential errors
* Enable null padding (null_padding=true) to pad missing columns with NULL values
* Check you are using the correct file compression, otherwise set it (e.g., compression = 'zstd')
* Be sure that the maximum line size is set to an appropriate value, otherwise set it (e.g., max_line_size=10000000)LINE 1: SELECT * FROM read_csv_auto('F:/Data/sales_sample.xlsx') LIMIT 10;
安装插件
INSTALL 'excel';
LOAD 'excel';
读取excel
D SELECT * FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet = 'orders');
┌──────────┬─────────────────────┬─────────────┬─────────┬───┬──────────┬──────────┬──────────┬────────────────┐
│ order_id │ order_date │ customer_id │ region │ … │ quantity │ discount │ amount │ payment_method │
│ varchar │ timestamp │ varchar │ varchar │ │ double │ double │ double │ varchar │
├──────────┼─────────────────────┼─────────────┼─────────┼───┼──────────┼──────────┼──────────┼────────────────┤
│ O0000001 │ 2024-04-24 00:00:00 │ C00466 │ 华南 │ … │ 3.0 │ 0.1 │ 12411.95 │ 现金 │
│ O0000002 │ 2024-10-01 00:00:00 │ C00288 │ 华南 │ … │ 5.0 │ 0.1 │ 5310.04 │ 支付宝 │
│ O0000003 │ 2024-10-10 00:00:00 │ C00353 │ 东北 │ … │ 4.0 │ 0.05 │ 18798.87 │ 支付宝 │
│ O0000004 │ 2025-07-04 00:00:00 │ C00164 │ 华北 │ … │ 1.0 │ 0.0 │ 105.43 │ 刷卡 │
│ O0000005 │ 2024-01-01 00:00:00 │ C00412 │ 西南 │ … │ 2.0 │ 0.05 │ 15200.0 │ 银联 │
│ O0000006 │ 2024-07-20 00:00:00 │ C00400 │ 华北 │ … │ 4.0 │ 0.0 │ 392.76 │ 现金 │
│ O0000007 │ 2025-10-19 00:00:00 │ C00016 │ 华中 │ … │ 1.0 │ 0.1 │ 174.32 │ 支付宝 │
│ O0000008 │ 2024-11-14 00:00:00 │ C00018 │ 东北 │ … │ 4.0 │ 0.0 │ 10442.36 │ 现金 │
│ O0000009 │ 2024-09-13 00:00:00 │ C00136 │ 西北 │ … │ 4.0 │ 0.1 │ 18960.01 │ 刷卡 │
│ O0000010 │ 2024-03-21 00:00:00 │ C00490 │ 东北 │ … │ 2.0 │ 0.05 │ 3060.25 │ 刷卡 │
│ O0000011 │ 2025-10-14 00:00:00 │ C00464 │ 华东 │ … │ 22.0 │ 0.0 │ 19029.34 │ 银联 │
│ O0000012 │ 2024-01-14 00:00:00 │ C00247 │ 华北 │ … │ 4.0 │ 0.05 │ 38000.0 │ 银联 │
│ O0000013 │ 2025-05-08 00:00:00 │ C00248 │ 华南 │ … │ 4.0 │ 0.0 │ 32000.0 │ 刷卡 │
│ O0000014 │ 2025-05-29 00:00:00 │ C00331 │ 华南 │ … │ 3.0 │ 0.0 │ 947.34 │ 微信支付 │
│ O0000015 │ 2024-06-12 00:00:00 │ C00198 │ 华中 │ … │ 1.0 │ 0.1 │ 198.68 │ 微信支付 │
│ O0000016 │ 2025-03-25 00:00:00 │ C00001 │ 西南 │ … │ 5.0 │ 0.0 │ 15696.5 │ 支付宝 │
│ O0000017 │ 2024-03-03 00:00:00 │ C00190 │ 华东 │ … │ 5.0 │ 0.05 │ 10281.04 │ 银联 │
│ O0000018 │ 2024-06-21 00:00:00 │ C00381 │ 华北 │ … │ 3.0 │ 0.05 │ 8987.42 │ 支付宝 │
│ O0000019 │ 2025-06-11 00:00:00 │ C00460 │ 西南 │ … │ 4.0 │ 0.1 │ 36000.0 │ 现金 │
│ O0000020 │ 2025-08-02 00:00:00 │ C00294 │ 西南 │ … │ 4.0 │ 0.0 │ 40000.0 │ 刷卡 │
│ · │ · │ · │ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │ · │ · │ · │
│ O0009986 │ 2025-07-10 00:00:00 │ C00221 │ 华南 │ … │ 3.0 │ 0.05 │ 570.0 │ 微信支付 │
│ O0009987 │ 2025-06-19 00:00:00 │ C00109 │ 华东 │ … │ 2.0 │ 0.0 │ 3572.32 │ 现金 │
│ O0009988 │ 2024-08-30 00:00:00 │ C00027 │ 华中 │ … │ 3.0 │ 0.0 │ 10073.76 │ 支付宝 │
│ O0009989 │ 2024-05-25 00:00:00 │ C00246 │ 西南 │ … │ 5.0 │ 0.0 │ 28842.9 │ 现金 │
│ O0009990 │ 2025-09-14 00:00:00 │ C00055 │ 东北 │ … │ 5.0 │ 0.05 │ 17410.22 │ 微信支付 │
│ O0009991 │ 2024-07-24 00:00:00 │ C00465 │ 华东 │ … │ 1.0 │ 0.0 │ 1317.38 │ 刷卡 │
│ O0009992 │ 2024-09-23 00:00:00 │ C00133 │ 华东 │ … │ 4.0 │ 0.0 │ 32000.0 │ 微信支付 │
│ O0009993 │ 2025-04-13 00:00:00 │ C00052 │ 华东 │ … │ 4.0 │ 0.0 │ 1703.56 │ 银联 │
│ O0009994 │ 2024-07-15 00:00:00 │ C00493 │ 华中 │ … │ 5.0 │ 0.05 │ 720.58 │ 刷卡 │
│ O0009995 │ 2025-07-02 00:00:00 │ C00253 │ 华北 │ … │ 4.0 │ 0.0 │ 257.92 │ 支付宝 │
│ O0009996 │ 2024-02-16 00:00:00 │ C00269 │ 西北 │ … │ 4.0 │ 0.0 │ 4800.0 │ 现金 │
│ O0009997 │ 2025-06-14 00:00:00 │ C00298 │ 东北 │ … │ 1.0 │ 0.0 │ 1141.6 │ 刷卡 │
│ O0009998 │ 2025-02-07 00:00:00 │ C00399 │ 华东 │ … │ 3.0 │ 0.0 │ 573.15 │ 刷卡 │
│ O0009999 │ 2025-01-18 00:00:00 │ C00079 │ 西南 │ … │ 3.0 │ 0.05 │ 9310.84 │ 银联 │
│ O0010000 │ 2024-11-03 00:00:00 │ C00018 │ 西南 │ … │ 2.0 │ 0.05 │ 4251.16 │ 支付宝 │
│ O0004695 │ 2025-04-19 00:00:00 │ C00430 │ 西南 │ … │ 2.0 │ 0.0 │ 6496.86 │ 微信支付 │
│ O0000221 │ 2025-03-28 00:00:00 │ C00211 │ 华东 │ … │ 1.0 │ 0.1 │ 71.16 │ 微信支付 │
│ O0002140 │ 2024-04-01 00:00:00 │ C00069 │ 华东 │ … │ 2.0 │ 0.2 │ 3225.97 │ 现金 │
│ O0004602 │ 2024-02-17 00:00:00 │ C00095 │ 华南 │ … │ 1.0 │ 0.0 │ 3934.85 │ 刷卡 │
│ O0007509 │ 2025-01-28 00:00:00 │ C00465 │ 东北 │ … │ 3.0 │ 0.0 │ 2393.52 │ 支付宝 │
├──────────┴─────────────────────┴─────────────┴─────────┴───┴──────────┴──────────┴──────────┴────────────────┤
│ 10005 rows (40 shown) 12 columns (8 shown) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
D SELECT * FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet = 'customers');
┌─────────────┬────────────┬─────────┬───────────┬─────────────────────┐
│ customer_id │ name │ city │ vip_level │ signup_date │
│ varchar │ varchar │ varchar │ varchar │ timestamp │
├─────────────┼────────────┼─────────┼───────────┼─────────────────────┤
│ C00001 │ 用户221958 │ 深圳 │ 银卡 │ 2024-07-07 00:00:00 │
│ C00002 │ 用户771155 │ 苏州 │ 普通 │ 2024-06-18 00:00:00 │
│ C00003 │ 用户231932 │ 成都 │ 普通 │ 2024-01-04 00:00:00 │
│ C00004 │ 用户465838 │ 成都 │ 普通 │ 2025-04-11 00:00:00 │
│ C00005 │ 用户359178 │ 长沙 │ 银卡 │ 2024-02-02 00:00:00 │
│ C00006 │ 用户744167 │ 青岛 │ 金卡 │ 2024-12-22 00:00:00 │
│ C00007 │ 用户210268 │ 杭州 │ 银卡 │ 2025-10-06 00:00:00 │
│ C00008 │ 用户832180 │ 北京 │ 普通 │ 2024-03-14 00:00:00 │
│ C00009 │ 用户154886 │ 西安 │ 金卡 │ 2025-08-22 00:00:00 │
│ C00010 │ 用户237337 │ 杭州 │ 普通 │ 2025-10-26 00:00:00 │
│ C00011 │ 用户621430 │ 青岛 │ 普通 │ 2024-02-11 00:00:00 │
│ C00012 │ 用户187498 │ 青岛 │ 普通 │ 2025-08-14 00:00:00 │
│ C00013 │ 用户999159 │ 青岛 │ 银卡 │ 2024-12-11 00:00:00 │
│ C00014 │ 用户275203 │ 杭州 │ 普通 │ 2025-08-30 00:00:00 │
│ C00015 │ 用户291335 │ 武汉 │ 银卡 │ 2025-01-17 00:00:00 │
│ C00016 │ 用户378167 │ 深圳 │ 金卡 │ 2025-03-19 00:00:00 │
│ C00017 │ 用户141090 │ 成都 │ 普通 │ 2024-12-30 00:00:00 │
│ C00018 │ 用户429365 │ 深圳 │ 普通 │ 2025-08-02 00:00:00 │
│ C00019 │ 用户164820 │ 广州 │ 普通 │ 2025-05-06 00:00:00 │
│ C00020 │ 用户887201 │ 武汉 │ 普通 │ 2024-12-15 00:00:00 │
│ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │
│ C00481 │ 用户694319 │ 苏州 │ 普通 │ 2025-03-24 00:00:00 │
│ C00482 │ 用户584299 │ 南京 │ 普通 │ 2024-02-16 00:00:00 │
│ C00483 │ 用户712210 │ 南京 │ 白金 │ 2025-03-04 00:00:00 │
│ C00484 │ 用户548982 │ 成都 │ 金卡 │ 2024-06-30 00:00:00 │
│ C00485 │ 用户224046 │ 西安 │ 普通 │ 2025-02-01 00:00:00 │
│ C00486 │ 用户506619 │ 长沙 │ 银卡 │ 2025-03-31 00:00:00 │
│ C00487 │ 用户132097 │ 北京 │ 银卡 │ 2024-06-03 00:00:00 │
│ C00488 │ 用户766326 │ 苏州 │ 普通 │ 2024-11-02 00:00:00 │
│ C00489 │ 用户749150 │ 深圳 │ 金卡 │ 2024-05-07 00:00:00 │
│ C00490 │ 用户330229 │ 北京 │ 金卡 │ 2024-02-05 00:00:00 │
│ C00491 │ 用户218834 │ 西安 │ 普通 │ 2024-09-10 00:00:00 │
│ C00492 │ 用户193848 │ 北京 │ 普通 │ 2024-08-31 00:00:00 │
│ C00493 │ 用户152921 │ 广州 │ 普通 │ 2025-10-28 00:00:00 │
│ C00494 │ 用户805086 │ 深圳 │ 普通 │ 2024-10-26 00:00:00 │
│ C00495 │ 用户750429 │ 西安 │ 普通 │ 2024-08-11 00:00:00 │
│ C00496 │ 用户805660 │ 成都 │ 金卡 │ 2025-01-23 00:00:00 │
│ C00497 │ 用户384821 │ 苏州 │ 普通 │ 2024-02-29 00:00:00 │
│ C00498 │ 用户155609 │ 青岛 │ 普通 │ 2025-10-06 00:00:00 │
│ C00499 │ 用户327897 │ 武汉 │ 金卡 │ 2025-03-01 00:00:00 │
│ C00500 │ 用户131024 │ 西安 │ 银卡 │ 2025-05-20 00:00:00 │
├─────────────┴────────────┴─────────┴───────────┴─────────────────────┤
│ 500 rows (40 shown) 5 columns │
└──────────────────────────────────────────────────────────────────────┘
4.3. 增加扩展和查看扩展信息
✅ 1. 什么是 DuckDB 扩展?
DuckDB 是一个「核心非常小」的内嵌式数据库,默认只包含基础 SQL 与存储功能。
扩展(Extension) 就是给 DuckDB 动态增加额外能力:
例如:
| 扩展 | 作用 |
|---|---|
excel | 读取 XLSX 文件(含 sheet 选择) |
parquet | 高性能读取/写入 Parquet |
json | JSON 解析、查询、导入 |
icu | 更多国际化时间/字符处理 |
fts | 全文检索 |
sqlite_scanner | 直接读取 SQLite 文件 |
postgres_scanner | 查询 PostgreSQL 数据 |
DuckDB 的扩展就像插件,按需加载、可在线安装、无需重启。
✅ 2. 查看当前扩展信息
✅ 当前有哪些扩展可用?
D SELECT * FROM duckdb_extensions();
┌──────────────────┬─────────┬───────────┬──────────────────────┬──────────────────────────────────────────────────────────────────┬───────────────────┬───────────────────┬───────────────────┬────────────────┐
│ extension_name │ loaded │ installed │ install_path │ description │ aliases │ extension_version │ install_mode │ installed_from │
│ varchar │ boolean │ boolean │ varchar │ varchar │ varchar[] │ varchar │ varchar │ varchar │
├──────────────────┼─────────┼───────────┼──────────────────────┼──────────────────────────────────────────────────────────────────┼───────────────────┼───────────────────┼───────────────────┼────────────────┤
│ autocomplete │ true │ true │ (BUILT-IN) │ Adds support for autocomplete in the shell │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ aws │ false │ false │ │ Provides features that depend on the AWS SDK │ [] │ │ NOT_INSTALLED │ │
│ azure │ false │ false │ │ Adds a filesystem abstraction for Azure blob storage to DuckDB │ [] │ │ NOT_INSTALLED │ │
│ core_functions │ true │ true │ (BUILT-IN) │ Core function library │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ delta │ false │ false │ │ Adds support for Delta Lake │ [] │ │ NOT_INSTALLED │ │
│ ducklake │ false │ false │ │ Adds support for DuckLake, SQL as a Lakehouse Format │ [] │ │ NOT_INSTALLED │ │
│ encodings │ false │ false │ │ All unicode encodings to UTF-8 │ [] │ │ NOT_INSTALLED │ │
│ excel │ true │ true │ C:\Users\caimingya… │ Adds support for Excel-like format strings │ [] │ 8504be9 │ REPOSITORY │ core │
│ fts │ false │ false │ │ Adds support for Full-Text Search Indexes │ [] │ │ NOT_INSTALLED │ │
│ httpfs │ false │ false │ │ Adds support for reading and writing files over a HTTP(S) conn… │ [http, https, s3] │ │ NOT_INSTALLED │ │
│ iceberg │ false │ false │ │ Adds support for Apache Iceberg │ [] │ │ NOT_INSTALLED │ │
│ icu │ true │ true │ (BUILT-IN) │ Adds support for time zones and collations using the ICU library │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ inet │ false │ false │ │ Adds support for IP-related data types and functions │ [] │ │ NOT_INSTALLED │ │
│ jemalloc │ false │ false │ │ Overwrites system allocator with JEMalloc │ [] │ │ NOT_INSTALLED │ │
│ json │ true │ true │ (BUILT-IN) │ Adds support for JSON operations │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ motherduck │ false │ false │ │ Enables motherduck integration with the system │ [md] │ │ NOT_INSTALLED │ │
│ mysql_scanner │ false │ false │ │ Adds support for connecting to a MySQL database │ [mysql] │ │ NOT_INSTALLED │ │
│ parquet │ true │ true │ (BUILT-IN) │ Adds support for reading and writing parquet files │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ postgres_scanner │ false │ false │ │ Adds support for connecting to a Postgres database │ [postgres] │ │ NOT_INSTALLED │ │
│ shell │ true │ true │ (BUILT-IN) │ │ [] │ v1.4.1 │ STATICALLY_LINKED │ │
│ spatial │ false │ false │ │ Geospatial extension that adds support for working with spatia… │ [] │ │ NOT_INSTALLED │ │
│ sqlite_scanner │ false │ false │ │ Adds support for reading and writing SQLite database files │ [sqlite, sqlite3] │ │ NOT_INSTALLED │ │
│ tpcds │ false │ false │ │ Adds TPC-DS data generation and query support │ [] │ │ NOT_INSTALLED │ │
│ tpch │ false │ false │ │ Adds TPC-H data generation and query support │ [] │ │ NOT_INSTALLED │ │
│ ui │ false │ false │ │ Adds local UI for DuckDB │ [] │ │ NOT_INSTALLED │ │
│ vss │ false │ false │ │ Adds indexing support to accelerate Vector Similarity Search │ [] │ │ NOT_INSTALLED │ │
├──────────────────┴─────────┴───────────┴──────────────────────┴──────────────────────────────────────────────────────────────────┴───────────────────┴───────────────────┴───────────────────┴────────────────┤
│ 26 rows 9 columns │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
✅ 3. 安装扩展(INSTALL)
如果扩展没有安装,使用:
INSTALL 'excel';
或多个扩展:
INSTALL 'json', 'icu', 'postgres_scanner';
✅ 作用:下载扩展(本地缓存或从 DuckDB 仓库自动获取)
✅ 执行一次即可,下次不需要再 INSTALL
✅ 4. 加载扩展(LOAD)
扩展安装完成后需要 LOAD 才能使用:
LOAD 'excel';
或多个:
LOAD 'parquet', 'json';
✅ 执行后才会注册扩展里的函数,比如 read_xlsx、read_json、read_parquet
✅ 加载后立刻生效,无需重启
✅ 5. 自动加载扩展
如果希望数据库启动时自动加载某扩展:
SET autoload_extensions = 'excel, json, parquet';
或永久写入配置(在文件驱动方式)
✅ 6. 卸载扩展(UNLOAD)
如果你已加载扩展但想卸载:
UNLOAD 'excel';
✅ 卸载后扩展提供的函数不可用
✅ 数据库不会崩溃,动态卸载安全
✅ 7. 删除扩展(UNINSTALL)
删除本地已安装扩展:
UNINSTALL 'excel';
✅ 会从本地插件目录删除扩展对象,如 .duckdb/extensions/excel.duckdb_extension
✅ 之后如需使用必须重新 INSTALL
✅ 8. 扩展存放位置
Windows / Linux / macOS 通常默认存放在:
~/.duckdb/extensions/
或者数据库本地目录,取决于运行模式。
✅ 9. 列举常用扩展及用途
| 扩展 | 用途 |
|---|---|
| parquet | 读写 Parquet,超高性能列式格式 |
| excel | 读 Excel,支持 sheet、范围、类型推断 |
| json | JSON 查询、导入、解析 |
| icu | 国际化字符、日期、时区函数 |
| fts | 全文检索(Full Text Search) |
| httpfs | 访问 HTTP/HTTPS、S3、Azure、GCS 存储 |
| sqlite_scanner | 直接查询 SQLite 文件 |
| postgres_scanner | 查询 PostgreSQL 数据库(零复制) |
| mysql_scanner | 查询 MySQL 数据库 |
| delta | 读取 Delta Lake |
| spatial | 空间地理数据,GIS 类功能(如 PostGIS) |
| tpcds / tpch | TPC 数据生成与测试(性能基准) |
很多功能(比如 parquet / json)在部分构建中是静态内置的,不需要安装。
✅ 10. 一句话总结
- INSTALL = 下载扩展
- LOAD = 启动扩展
- UNLOAD = 停用扩展
- UNINSTALL = 删除扩展
DuckDB 的扩展系统让它从一个精简 SQL 引擎变成一个 本地小型数据分析平台。
4.4. 将数据存储到duckdb中
D CREATE OR REPLACE TABLE orders AS
路 SELECT *
路 FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
D
D -- 建 customers 表
D CREATE OR REPLACE TABLE customers AS
路 SELECT *
路 FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='customers');
D
D SELECT 'orders' AS tbl, COUNT(*) AS rows FROM orders
路 UNION ALL
路 SELECT 'customers', COUNT(*) FROM customers;
┌───────────┬───────┐
│ tbl │ rows │
│ varchar │ int64 │
├───────────┼───────┤
│ orders │ 10005 │
│ customers │ 500 │
└───────────┴───────┘
D
D DESCRIBE orders;
┌────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ order_id │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ order_date │ TIMESTAMP │ YES │ NULL │ NULL │ NULL │
│ customer_id │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ region │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ channel │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ category │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ product │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ unit_price │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ quantity │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ discount │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ amount │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ payment_method │ VARCHAR │ YES │ NULL │ NULL │ NULL │
├────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┤
│ 12 rows 6 columns │
└──────────────────────────────────────────────────────────────────────┘
D DESCRIBE customers;
┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ customer_id │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ name │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ city │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ vip_level │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ signup_date │ TIMESTAMP │ YES │ NULL │ NULL │ NULL │
└─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
D
D SELECT * FROM orders LIMIT 5;
┌──────────┬─────────────────────┬─────────────┬─────────┬─────────┬──────────┬──────────┬────────────┬──────────┬──────────┬──────────┬────────────────┐
│ order_id │ order_date │ customer_id │ region │ channel │ category │ product │ unit_price │ quantity │ discount │ amount │ payment_method │
│ varchar │ timestamp │ varchar │ varchar │ varchar │ varchar │ varchar │ double │ double │ double │ double │ varchar │
├──────────┼─────────────────────┼─────────────┼─────────┼─────────┼──────────┼──────────┼────────────┼──────────┼──────────┼──────────┼────────────────┤
│ O0000001 │ 2024-04-24 00:00:00 │ C00466 │ 华南 │ App │ 手机数码 │ 相机 │ 4597.02 │ 3.0 │ 0.1 │ 12411.95 │ 现金 │
│ O0000002 │ 2024-10-01 00:00:00 │ C00288 │ 华南 │ 门店 │ 办公用品 │ 显示器 │ 1180.01 │ 5.0 │ 0.1 │ 5310.04 │ 支付宝 │
│ O0000003 │ 2024-10-10 00:00:00 │ C00353 │ 东北 │ 门店 │ 手机数码 │ 智能手表 │ 4947.07 │ 4.0 │ 0.05 │ 18798.87 │ 支付宝 │
│ O0000004 │ 2025-07-04 00:00:00 │ C00164 │ 华北 │ 电销 │ 生鲜食品 │ 鸡蛋 │ 105.43 │ 1.0 │ 0.0 │ 105.43 │ 刷卡 │
│ O0000005 │ 2024-01-01 00:00:00 │ C00412 │ 西南 │ App │ 手机数码 │ 平板 │ 8000.0 │ 2.0 │ 0.05 │ 15200.0 │ 银联 │
└──────────┴─────────────────────┴─────────────┴─────────┴─────────┴──────────┴──────────┴────────────┴──────────┴──────────┴──────────┴────────────────┘
D SELECT * FROM customers LIMIT 5;
┌─────────────┬────────────┬─────────┬───────────┬─────────────────────┐
│ customer_id │ name │ city │ vip_level │ signup_date │
│ varchar │ varchar │ varchar │ varchar │ timestamp │
├─────────────┼────────────┼─────────┼───────────┼─────────────────────┤
│ C00001 │ 用户221958 │ 深圳 │ 银卡 │ 2024-07-07 00:00:00 │
│ C00002 │ 用户771155 │ 苏州 │ 普通 │ 2024-06-18 00:00:00 │
│ C00003 │ 用户231932 │ 成都 │ 普通 │ 2024-01-04 00:00:00 │
│ C00004 │ 用户465838 │ 成都 │ 普通 │ 2025-04-11 00:00:00 │
│ C00005 │ 用户359178 │ 长沙 │ 银卡 │ 2024-02-02 00:00:00 │
└─────────────┴────────────┴─────────┴───────────┴─────────────────────┘
4.5. 统计计算数据
4.5.1. 基础业务指标(KPI)
SELECTCOUNT(*) AS total_orders, -- 订单数SUM(amount) AS total_revenue, -- 销售额AVG(amount) AS avg_order_value, -- 平均客单价SUM(GREATEST(quantity,0)) AS total_qty, -- 总销量(负数/0按0计)COUNT_IF(amount > 0) AS paid_txn_cnt, -- 交易笔数COUNT(DISTINCT customer_id) AS buyers -- 下单人数
FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
D SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM orders;
┌──────────────┬───────────────────┬───────────────────┬───────────┬──────────────┬────────┐
│ total_orders │ total_revenue │ avg_order_value │ total_qty │ paid_txn_cnt │ buyers │
│ int64 │ double │ double │ double │ int128 │ int64 │
├──────────────┼───────────────────┼───────────────────┼───────────┼──────────────┼────────┤
│ 10005 │ 94118663.72999957 │ 9407.162791604154 │ 35252.0 │ 10005 │ 500 │
└──────────────┴───────────────────┴───────────────────┴───────────┴──────────────┴────────┘
D SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
┌──────────────┬───────────────────┬───────────────────┬───────────┬──────────────┬────────┐
│ total_orders │ total_revenue │ avg_order_value │ total_qty │ paid_txn_cnt │ buyers │
│ int64 │ double │ double │ double │ int128 │ int64 │
├──────────────┼───────────────────┼───────────────────┼───────────┼──────────────┼────────┤
│ 10005 │ 94118663.72999957 │ 9407.162791604154 │ 35252.0 │ 10005 │ 500 │
└──────────────┴───────────────────┴───────────────────┴───────────┴──────────────┴────────┘
D EXPLAIN ANALYZE SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.0569s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ UNGROUPED_AGGREGATE │
│ ──────────────────── │
│ Aggregates: │
│ count_star() │
│ sum(#0) │
│ avg(#1) │
│ sum(#2) │
│ count_if(#3) │
│ count(DISTINCT #4) │
│ │
│ 1 row │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ amount │
│ amount │
│ greatest(quantity, 0.0) │
│ (amount > 0.0) │
│ customer_id │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #10 │
│ #8 │
│ #2 │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ TABLE_SCAN │
│ ──────────────────── │
│ Function: READ_XLSX │
│ │
│ 10,005 rows │
│ (0.04s) │
└───────────────────────────┘
D EXPLAIN ANALYZE SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM orders;
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE SELECT COUNT(*) AS total_orders,SUM(amount) AS total_revenue,AVG(amount) AS avg_order_value,SUM(GREATEST(quantity,0)) AS total_qty,COUNT_IF(amount > 0)AS paid_txn_cnt,COUNT(DISTINCT customer_id) AS buyers FROM orders;
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.0026s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ UNGROUPED_AGGREGATE │
│ ──────────────────── │
│ Aggregates: │
│ count_star() │
│ sum(#0) │
│ avg(#1) │
│ sum(#2) │
│ count_if(#3) │
│ count(DISTINCT #4) │
│ │
│ 1 row │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ amount │
│ amount │
│ greatest(quantity, 0.0) │
│ true │
│ customer_id │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ TABLE_SCAN │
│ ──────────────────── │
│ Table: orders │
│ Type: Sequential Scan │
│ │
│ Projections: │
│ amount │
│ quantity │
│ customer_id │
│ │
│ 10,005 rows │
│ (0.00s) │
└───────────────────────────┘
4.5.2. 分组维度分析(地区/渠道/品类/支付方式)
D SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') GROUP BY region ORDER BY revenue DESC;
┌─────────┬────────┬────────────────────┬─────────┐
│ region │ orders │ revenue │ aov │
│ varchar │ int64 │ double │ double │
├─────────┼────────┼────────────────────┼─────────┤
│ 西南 │ 1509 │ 14943416.700000009 │ 9902.86 │
│ 华中 │ 1491 │ 14459096.130000023 │ 9697.58 │
│ 东北 │ 1450 │ 13836316.490000011 │ 9542.29 │
│ 西北 │ 1392 │ 12942988.790000029 │ 9298.12 │
│ 华东 │ 1410 │ 12763540.570000004 │ 9052.16 │
│ 华北 │ 1382 │ 12664126.859999996 │ 9163.62 │
│ 华南 │ 1371 │ 12509178.189999994 │ 9124.13 │
└─────────┴────────┴────────────────────┴─────────┘
D SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM orders GROUP BY region ORDER BY revenue DESC;
┌─────────┬────────┬────────────────────┬─────────┐
│ region │ orders │ revenue │ aov │
│ varchar │ int64 │ double │ double │
├─────────┼────────┼────────────────────┼─────────┤
│ 西南 │ 1509 │ 14943416.700000009 │ 9902.86 │
│ 华中 │ 1491 │ 14459096.130000023 │ 9697.58 │
│ 东北 │ 1450 │ 13836316.490000011 │ 9542.29 │
│ 西北 │ 1392 │ 12942988.790000029 │ 9298.12 │
│ 华东 │ 1410 │ 12763540.570000004 │ 9052.16 │
│ 华北 │ 1382 │ 12664126.859999996 │ 9163.62 │
│ 华南 │ 1371 │ 12509178.189999994 │ 9124.13 │
└─────────┴────────┴────────────────────┴─────────┘
D EXPLAIN ANALYZE SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') GROUP BY region ORDER BY revenue DESC;
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') GROUP BY region ORDER BY revenue DESC;
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.0899s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ ORDER_BY │
│ ──────────────────── │
│ sum(read_xlsx.amount) DESC│
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ region │
│ orders │
│ revenue │
│ aov │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_GROUP_BY │
│ ──────────────────── │
│ Groups: #0 │
│ │
│ Aggregates: │
│ count_star() │
│ sum(#1) │
│ avg(#2) │
│ │
│ 7 rows │
│ (0.01s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ region │
│ amount │
│ amount │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #3 │
│ #10 │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ TABLE_SCAN │
│ ──────────────────── │
│ Function: READ_XLSX │
│ │
│ 10,005 rows │
│ (0.07s) │
└───────────────────────────┘
D
D EXPLAIN ANALYZE SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM orders GROUP BY region ORDER BY revenue DESC;
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE SELECT region,COUNT(*) AS orders,SUM(amount) AS revenue,ROUND(AVG(amount),2) AS aov FROM orders GROUP BY region ORDER BY revenue DESC;
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.0031s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ ORDER_BY │
│ ──────────────────── │
│ sum(memory.main.orders │
│ .amount) DESC │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_compress_string_│
│ ubigint(#0) │
│ #1 │
│ #2 │
│ #3 │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ region │
│ orders │
│ revenue │
│ aov │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_GROUP_BY │
│ ──────────────────── │
│ Groups: #0 │
│ │
│ Aggregates: │
│ count_star() │
│ sum(#1) │
│ avg(#2) │
│ │
│ 7 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ region │
│ amount │
│ amount │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_compress_string_│
│ ubigint(#0) │
│ #1 │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ TABLE_SCAN │
│ ──────────────────── │
│ Table: orders │
│ Type: Sequential Scan │
│ │
│ Projections: │
│ region │
│ amount │
│ │
│ 10,005 rows │
│ (0.00s) │
└───────────────────────────┘
4.5.2. 新客 vs 老客(定义:用户在其“首单当月”为新客)
D EXPLAIN ANALYZE
路 WITH first_order AS (
路 SELECT
路 customer_id,
路 MIN(order_date) AS first_dt
路 FROM orders
路 GROUP BY customer_id
路 ),
路 labeled AS (
路 SELECT
路 o.*,
路 CASE WHEN date_trunc('month', o.order_date) = date_trunc('month', f.first_dt)
路 THEN '新客'
路 ELSE '老客'
路 END AS cust_tag
路 FROM orders o
路 JOIN first_order f USING (customer_id)
路 )
路 SELECT
路 cust_tag,
路 COUNT(*) AS orders,
路 COUNT(DISTINCT customer_id) AS buyers,
路 SUM(amount) AS revenue,
路 ROUND(AVG(amount),2) AS aov
路 FROM labeled
路 GROUP BY cust_tag
路 ORDER BY revenue DESC;
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE WITH first_order AS ( SELECT customer_id, MIN(order_date) AS first_dt FROM orders GROUP BY customer_id ), labeled AS ( SELECT o.*, CASE WHEN date_trunc('month', o.order_date) = date_trunc('month', f.first_dt) THEN '新客' ELSE '老客' END AS cust_tag FROM orders o JOIN first_order f USING (customer_id) ) SELECT cust_tag, COUNT(*) AS orders, COUNT(DISTINCT customer_id) AS buyers, SUM(amount) AS revenue, ROUND(AVG(amount),2) AS aov FROM labeled GROUP BY cust_tag ORDER BY revenue DESC;
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.0117s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ ORDER_BY │
│ ──────────────────── │
│ sum(labeled.amount) DESC │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_compress_string_│
│ ubigint(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ cust_tag │
│ orders │
│ buyers │
│ revenue │
│ aov │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_GROUP_BY │
│ ──────────────────── │
│ Groups: #0 │
│ │
│ Aggregates: │
│ count_star() │
│ count(DISTINCT #1) │
│ sum(#2) │
│ avg(#3) │
│ │
│ 2 rows │
│ (0.01s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ cust_tag │
│ customer_id │
│ amount │
│ amount │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #0 │
│ #1 │
│__internal_compress_string_│
│ ubigint(#2) │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ customer_id │
│ amount │
│ cust_tag │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_JOIN │
│ ──────────────────── │
│ Join Type: INNER │
│ │
│ Conditions: ├──────────────┐
│ customer_id = customer_id │ │
│ │ │
│ 10,005 rows │ │
│ (0.01s) │ │
└─────────────┬─────────────┘ │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│ TABLE_SCAN ││ PROJECTION │
│ ──────────────────── ││ ──────────────────── │
│ Table: orders ││__internal_decompress_strin│
│ Type: Sequential Scan ││ g(#0) │
│ ││ #1 │
│ Projections: ││ │
│ customer_id ││ │
│ order_date ││ │
│ amount ││ │
│ ││ │
│ 10,005 rows ││ 500 rows │
│ (0.00s) ││ (0.00s) │
└───────────────────────────┘└─────────────┬─────────────┘┌─────────────┴─────────────┐│ HASH_GROUP_BY ││ ──────────────────── ││ Groups: #0 ││ Aggregates: min(#1) ││ ││ 500 rows ││ (0.01s) │└─────────────┬─────────────┘┌─────────────┴─────────────┐│ PROJECTION ││ ──────────────────── ││ customer_id ││ order_date ││ ││ 10,005 rows ││ (0.00s) │└─────────────┬─────────────┘┌─────────────┴─────────────┐│ PROJECTION ││ ──────────────────── ││__internal_compress_string_││ ubigint(#0) ││ #1 ││ ││ 10,005 rows ││ (0.00s) │└─────────────┬─────────────┘┌─────────────┴─────────────┐│ TABLE_SCAN ││ ──────────────────── ││ Table: orders ││ Type: Sequential Scan ││ ││ Projections: ││ customer_id ││ order_date ││ ││ 10,005 rows ││ (0.00s) │└───────────────────────────┘
D
D
D
D EXPLAIN ANALYZE
路 WITH first_order AS (
路 SELECT
路 customer_id,
路 MIN(order_date) AS first_dt
路 FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders')
路 GROUP BY customer_id
路 ),
路 labeled AS (
路 SELECT
路 o.*,
路 CASE WHEN date_trunc('month', o.order_date) = date_trunc('month', f.first_dt)
路 THEN '新客'
路 ELSE '老客'
路 END AS cust_tag
路 FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') o
路 JOIN first_order f USING (customer_id)
路 )
路 SELECT
路 cust_tag,
路 COUNT(*) AS orders,
路 COUNT(DISTINCT customer_id) AS buyers,
路 SUM(amount) AS revenue,
路 ROUND(AVG(amount),2) AS aov
路 FROM labeled
路 GROUP BY cust_tag
路 ORDER BY revenue DESC;
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││ Query Profiling Information ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYZE WITH first_order AS ( SELECT customer_id, MIN(order_date) AS first_dt FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') GROUP BY customer_id ), labeled AS ( SELECT o.*, CASE WHEN date_trunc('month', o.order_date) = date_trunc('month', f.first_dt) THEN '新客' ELSE '老客' END AS cust_tag FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') o JOIN first_order f USING (customer_id) ) SELECT cust_tag, COUNT(*) AS orders, COUNT(DISTINCT customer_id) AS buyers, SUM(amount) AS revenue, ROUND(AVG(amount),2) AS aov FROM labeled GROUP BY cust_tag ORDER BY revenue DESC;
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││ Total Time: 0.160s ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│ QUERY │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ EXPLAIN_ANALYZE │
│ ──────────────────── │
│ 0 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ ORDER_BY │
│ ──────────────────── │
│ sum(labeled.amount) DESC │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_compress_string_│
│ ubigint(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ cust_tag │
│ orders │
│ buyers │
│ revenue │
│ aov │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│__internal_decompress_strin│
│ g(#0) │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ │
│ 2 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_GROUP_BY │
│ ──────────────────── │
│ Groups: #0 │
│ │
│ Aggregates: │
│ count_star() │
│ count(DISTINCT #1) │
│ sum(#2) │
│ avg(#3) │
│ │
│ 2 rows │
│ (0.01s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ cust_tag │
│ customer_id │
│ amount │
│ amount │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #0 │
│ #1 │
│__internal_compress_string_│
│ ubigint(#2) │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ──────────────────── │
│ customer_id │
│ amount │
│ cust_tag │
│ │
│ 10,005 rows │
│ (0.00s) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_JOIN │
│ ──────────────────── │
│ Join Type: INNER │
│ │
│ Conditions: ├──────────────┐
│ customer_id = customer_id │ │
│ │ │
│ 10,005 rows │ │
│ (0.01s) │ │
└─────────────┬─────────────┘ │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│ PROJECTION ││ HASH_GROUP_BY │
│ ──────────────────── ││ ──────────────────── │
│ #2 ││ Groups: #0 │
│ #0 ││ Aggregates: min(#1) │
│ #1 ││ │
│ #3 ││ │
│ #4 ││ │
│ #5 ││ │
│ #6 ││ │
│ #7 ││ │
│ #8 ││ │
│ #9 ││ │
│ #10 ││ │
│ #11 ││ │
│ ││ │
│ 10,005 rows ││ 500 rows │
│ (0.00s) ││ (0.01s) │
└─────────────┬─────────────┘└─────────────┬─────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│ TABLE_SCAN ││ PROJECTION │
│ ──────────────────── ││ ──────────────────── │
│ Function: READ_XLSX ││ customer_id │
│ ││ order_date │
│ ││ │
│ 10,005 rows ││ 10,005 rows │
│ (0.08s) ││ (0.00s) │
└───────────────────────────┘└─────────────┬─────────────┘┌─────────────┴─────────────┐│ PROJECTION ││ ──────────────────── ││ #2 ││ #1 ││ ││ 10,005 rows ││ (0.00s) │└─────────────┬─────────────┘┌─────────────┴─────────────┐│ TABLE_SCAN ││ ──────────────────── ││ Function: READ_XLSX ││ ││ 10,005 rows ││ (0.05s) │└───────────────────────────┘
4.6. 统计对比结果
4.6.1. 对比结论(同一批数据,结果一致)
-
直接读取 Excel(
read_xlsx)与读取已落库表(orders)在数值结果上完全一致(KPI、分组汇总等)。 -
性能差异显著:落库后查询明显更快(~14×–29×),主要开销来自
READ_XLSX的解析与转换。
4.6.2. 性能对比(你贴的 EXPLAIN ANALYZE)
| 场景 | SQL 对象 | Total Time | 加速倍数(落库/Excel) |
|---|---|---|---|
| 基础 KPI | read_xlsx('…', sheet='orders') | 0.0569 s | |
orders(落库表) | 0.0026 s | ≈ 21.9× | |
| 按地区汇总 | read_xlsx('…', sheet='orders') | 0.0899 s | |
orders(落库表) | 0.0031 s | ≈ 29.0× | |
| 新客 vs 老客 | 纯落库表版 CTE | 0.0117 s | |
全程 read_xlsx 版 | 0.1600 s | ≈ 13.7×(落库更快) |
注:倍数=(Excel耗时 ÷ 落库耗时);你的执行日志中
TABLE_SCAN Function: READ_XLSX约耗时 0.04–0.08 s,是主要差异来源。
4.6.3. 结果一致性(摘录)
-
KPI 两种方式数值一致:
-
total_orders=10005 -
total_revenue=94,118,663.73 -
avg_order_value≈9407.16 -
total_qty=35,252 -
paid_txn_cnt=10005 -
buyers=500
-
-
按地区汇总两种方式排序、金额完全一致(西南、华中、东北…)。
4.6.4. 为什么落库更快?
-
read_xlsx每次查询都要 解析 XLSX(ZIP+XML)→ 类型推断 → 转换成列批;无法充分复用缓存。 -
落库表/本地
.duckdb:-
数据以 列式、压缩、字典编码 持久化,扫描和聚合开销极小;
-
DuckDB 可复用 buffer cache,算子更少、数据就绪。
-
4.6.5. 最佳实践建议
-
一次性落库或转 Parquet,后续所有分析用表/Parquet:
-- 落库(你已完成) CREATE OR REPLACE TABLE orders AS SELECT * FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');-- 可选:转 Parquet(便于共享与分层存储) COPY (SELECT * FROM orders) TO 'F:/Data/orders.parquet' (FORMAT PARQUET); -
频繁分析/联表/时间序列 → 用落库表或 Parquet;偶尔一次性查看 → 可以
read_xlsx快速扫。 -
需要更高吞吐或共享 → 湖格式(Parquet)+ DuckDB 外表;还可叠加分区/分桶。
4.7. 持久化存储
4.7.1. 内存模式切换为文件模式
D ATTACH 'F:/Data/sales_demo.duckdb' AS dst;
D PRAGMA database_list;
┌───────┬─────────┬───────────────────────────┐
│ seq │ name │ file │
│ int64 │ varchar │ varchar │
├───────┼─────────┼───────────────────────────┤
│ 592 │ memory │ NULL │
│ 2071 │ dst │ F:/Data/sales_demo.duckdb │
└───────┴─────────┴───────────────────────────┘
D CREATE OR REPLACE TABLE dst.orders AS SELECT * FROM main.orders;
D CREATE OR REPLACE TABLE dst.customers AS SELECT * FROM main.customers;
D SHOW TABLES FROM dst;
┌───────────┐
│ name │
│ varchar │
├───────────┤
│ customers │
│ orders │
└───────────┘
D SELECT COUNT(*) FROM dst.orders;
┌──────────────┐
│ count_star() │
│ int64 │
├──────────────┤
│ 10005 │
└──────────────┘
4.7.2. 打开duckdb
C:\Users\caimingyang>duckdb "F:/Data/sales_demo.duckdb"
DuckDB v1.4.1 (Andium) b390a7c376
Enter ".help" for usage hints.
D SHOW TABLES FROM dst;
Catalog Error:
SHOW TABLES FROM: No catalog + schema named ".dst" found.
D SHOW TABLES
路 ;
┌───────────┐
│ name │
│ varchar │
├───────────┤
│ customers │
│ orders │
└───────────┘
D SHOW TABLES;
┌───────────┐
│ name │
│ varchar │
├───────────┤
│ customers │
│ orders │
└───────────┘
D
4.8. ETL数据处理环节
4.8.1. 抽取(E:Extract)
(你已经用 read_xlsx 读入,以下仅列备选)
-- Excel
INSTALL 'excel'; LOAD 'excel';
SELECT * FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders');
4.8.2. 清洗/转换(T:Transform)
4.8.2.1 典型“数据健康”修复
-- 2.1.1 去重(示例:以 order_id 最新记录为准)
CREATE OR REPLACE TABLE stg_orders AS
WITH ranked AS (SELECT o.*,ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY order_date DESC) AS rnFROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='orders') o
)
SELECT * FROM ranked WHERE rn = 1;-- 2.1.2 处理异常 & 缺失:负数/0数量、折扣空值、支付方式空
CREATE OR REPLACE TABLE stg_orders_clean AS
SELECTorder_id,order_date::TIMESTAMP AS order_date,customer_id,region, channel, category, product,unit_price,CASE WHEN quantity < 0 THEN 0 ELSE quantity END AS quantity, -- 负数→0(可改“退货单”策略)COALESCE(discount, 0) AS discount,-- 重新计算金额,避免原始 amount 异常ROUND(unit_price * CASE WHEN quantity < 0 THEN 0 ELSE quantity END * (1 - COALESCE(discount,0)), 2) AS amount,COALESCE(payment_method, '(缺失)') AS payment_method
FROM stg_orders;
4.8.2.2 维表同步(customers)
CREATE OR REPLACE TABLE stg_customers AS
SELECT * FROM read_xlsx('F:/Data/sales_sample.xlsx', sheet='customers');
4.8.2.3 维度派生/口径统一(示例)
-- 增加年月字段、VIP分组
CREATE OR REPLACE TABLE fct_orders AS
SELECT*,date_trunc('month', order_date) AS month,CASEWHEN amount >= 20000 THEN '高额订单'WHEN amount >= 5000 THEN '中额订单'ELSE '普通订单'END AS order_bucket
FROM stg_orders_clean;CREATE OR REPLACE TABLE dim_customers AS
SELECT*,CASE vip_levelWHEN '白金' THEN 'VIP4'WHEN '金卡' THEN 'VIP3'WHEN '银卡' THEN 'VIP2'ELSE 'VIP1'END AS vip_tier
FROM stg_customers;
4.8.3. 落库(L:Load)
(把清洗后的数据固化为 DuckDB 本地表,之后查询更快)
-- 一般我们使用一个库文件(例如 F:/Data/sales_demo.duckdb)启动 DuckDB 后执行:
CREATE SCHEMA IF NOT EXISTS etl;CREATE OR REPLACE TABLE etl.orders AS SELECT * FROM fct_orders;
CREATE OR REPLACE TABLE etl.customers AS SELECT * FROM dim_customers;-- 索引(加速过滤/关联)
CREATE INDEX IF NOT EXISTS idx_orders_cust ON etl.orders(customer_id);
CREATE INDEX IF NOT EXISTS idx_orders_date ON etl.orders(order_date);
CREATE INDEX IF NOT EXISTS idx_cust_id ON etl.customers(customer_id);
目录化建议:
stg_*为“过渡(中间层)”,etl.*为清洗后的成品层。
4.8.4. 导出(多种方式)
4.8.4.1 导出为 Excel
COPY (SELECT * FROM etl.orders
)
TO 'F:/Data/exports/orders.xlsx'
WITH (FORMAT XLSX, SHEET 'orders');
4.9. 视图
DuckDB 支持 视图(VIEW) 与 临时视图(TEMP VIEW)。
-
VIEW:保存 SQL 定义的逻辑对象,不存数据;查询时实时计算。
-
TEMP VIEW:会话级视图,断开连接即消失。
-
物化视图(Materialized View):DuckDB 当前不提供官方 MV 语法(可用“表 + 定期刷新”替代)。
4.9.1 创建视图(示例:KPI 汇总)
D CREATE OR REPLACE VIEW rpt_kpi AS
路 SELECT
路 COUNT(*) AS total_orders,
路 SUM(amount) AS total_revenue,
路 AVG(amount) AS avg_order_value,
路 COUNT(DISTINCT customer_id) AS buyers
路 FROM orders;
D select * from rpt_kpi
路 ;
┌──────────────┬───────────────────┬───────────────────┬────────┐
│ total_orders │ total_revenue │ avg_order_value │ buyers │
│ int64 │ double │ double │ int64 │
├──────────────┼───────────────────┼───────────────────┼────────┤
│ 10005 │ 94118663.72999957 │ 9407.162791604154 │ 500 │
└──────────────┴───────────────────┴───────────────────┴────────┘
DC:\Users\caimingyang>duckdb "F:/Data/sales_demo.duckdb"
DuckDB v1.4.1 (Andium) b390a7c376
Enter ".help" for usage hints.
D select * from rpt_kpi;
┌──────────────┬───────────────────┬───────────────────┬────────┐
│ total_orders │ total_revenue │ avg_order_value │ buyers │
│ int64 │ double │ double │ int64 │
├──────────────┼───────────────────┼───────────────────┼────────┤
│ 10005 │ 94118663.72999957 │ 9407.162791604154 │ 500 │
└──────────────┴───────────────────┴───────────────────┴────────┘
4.9.2. 临时视图(会话内复用中间结果)
CREATE TEMP VIEW v_first_order AS
SELECT customer_id, MIN(order_date) AS first_dt
FROM orders GROUP BY customer_id;-- 使用
SELECT o.customer_id, o.order_id
FROM etl.orders o JOIN v_first_order f USING (customer_id)
WHERE date_trunc('month', o.order_date) = date_trunc('month', f.first_dt);
4.9.3. “物化视图”的替代
-- 以表形式固化 + 定时刷新(脚本/调度器)
CREATE OR REPLACE TABLE mv_monthly_revenue AS
SELECT date_trunc('month', order_date) AS month, SUM(amount) AS revenue
FROM orders
GROUP BY 1;-- “刷新”:先删再建,或 MERGE 增量(按当月/最近N月重算)
DELETE FROM mv_monthly_revenue WHERE month >= date_trunc('month', now()) - INTERVAL '3 months';
INSERT INTO mv_monthly_revenue
SELECT date_trunc('month', order_date), SUM(amount)
FROM orders
WHERE order_date >= date_trunc('month', now()) - INTERVAL '3 months'
GROUP BY 1;
