模拟数据
本页目的: 用纯 SQL 构造贴近真实分析场景的模拟数据 —— 不用写 Python 循环, 全部在 DuckDB 里生成。产物(表 + Parquet)同时是 PostgreSQL 加速查询 实验的数据源。
本页是 DuckDB 实战系列第 2 篇,上一篇见 环境与基本使用。
1. 内置生成器
generate_series 生成序号,random() 生成随机数,md5() 造假字符串:
-- 序号(注意:generate_series 返回 BIGINT,DATE 加法只接受 INTEGER,需显式转)
SELECT i, i*i AS square FROM generate_series(1, 5) t(i);
SELECT DATE '2026-01-01' + i::INTEGER AS d FROM generate_series(0, 4) t(i);
-- 随机值与假 hash
SELECT i, md5(random()::varchar) AS fake_hash FROM generate_series(1, 3) t(i);
⚠️ 坑:
DATE + i(i 来自generate_series)报错No function matches ... '+(DATE, BIGINT)'——generate_series返回 BIGINT, 而 DATE 加法只接受 INTEGER,需要i::INTEGER。这是本页生成脚本里最容易踩的错。
2. 迷你电商数仓(纯 SQL 生成)
一套 4 张表的订单模型,行数目标:10 万客户 / 100 万订单 / 200 万明细, 订单日期随机分布在最近 2 年:
-- 客户:id、姓名、城市、注册日期
CREATE TABLE customers AS
SELECT
i AS id,
'客户' || i AS name,
(array['上海','北京','深圳','广州','杭州','成都'])[1 + (i % 6)] AS city,
DATE '2024-08-11' + CAST(i % 730 AS INTEGER) AS signup_date
FROM generate_series(1, 100000) t(i);
-- 商品:id、名称、分类、价格
CREATE TABLE products AS
SELECT
i AS id,
'产品' || i AS name,
(array['数码','家电','服饰','食品','图书'])[1 + (i % 5)] AS category,
round(10 + (i % 900) * 0.1, 2) AS price
FROM generate_series(1, 1000) t(i);
-- 订单:id、客户、下单日期(随机 2 年)、金额、状态
CREATE TABLE orders AS
SELECT
i AS id,
1 + (i % 100000) AS customer_id,
DATE '2024-08-11' + CAST(random() * 730 AS INTEGER) AS order_date,
round(10 + random() * 990, 2) AS amount,
(array['pending','paid','shipped','done','cancelled'])[1 + (i % 5)] AS status
FROM generate_series(1, 1000000) t(i);
-- 明细:id、订单、商品、数量、单价
CREATE TABLE order_items AS
SELECT
i AS id,
1 + (i % 1000000) AS order_id,
1 + ((i * 7) % 1000) AS product_id,
1 + (i % 5) AS qty,
round(10 + ((i * 13) % 500), 2) AS unit_price
FROM generate_series(1, 2000000) t(i);
生成结果(本机实测,秒级完成):
| 表 | 行数 |
|---|---|
| customers | 100,000 |
| products | 1,000 |
| orders | 1,000,000 |
| order_items | 2,000,000 |
马上可以跑真实分析查询(JOIN + 聚合 + 排序):
SELECT c.city, o.status,
count(*) AS order_cnt, round(sum(o.amount), 2) AS total_amount
FROM orders o JOIN customers c ON o.customer_id = c.id
WHERE o.order_date >= DATE '2026-02-01'
GROUP BY c.city, o.status
ORDER BY total_amount DESC
LIMIT 8;
💡 补充:外键 —— 上面 4 张表都是
CREATE TABLE ... AS(CTAS)建的,CTAS 不带 任何约束(连主键都没有),所以这套迷你数仓里没有外键,引用一致性靠生成逻辑保证 (如orders.customer_id = 1 + (i % 100000)恰好落在 1..100000 区间内)。DuckDB 支持外键语法,且本机 1.5.5 实测强制校验(插入父行不存在的记录报
Constraint Error: Violates foreign key constraint)。但有几点限制(本机实测):
- 外键只能建表时声明(内联
REFERENCES/FOREIGN KEY (...)),ALTER TABLE ... ADD FOREIGN KEY不支持(报Not implemented)- 不支持
ON DELETE CASCADE/SET NULL/SET DEFAULT,删除被引用的父行 默认拒绝(RESTRICT 行为)- 被引用列必须是
PRIMARY KEY或UNIQUE所以本页生成脚本保持 CTAS 不带约束即可;若要严格外键,需把建表语句改成显式
CREATE TABLE+REFERENCES声明,例如订单表挂到客户表(其余列按需补上,此处只示意思路):CREATE TABLE customers ( id INTEGER PRIMARY KEY, name VARCHAR ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER REFERENCES customers(id) -- 外键:指向 customers.id ); -- 多列外键的写法(示意:前提是 orders 有 (id, line_no) 复合主键): CREATE TABLE order_lines ( order_id INTEGER, line_no INTEGER, FOREIGN KEY (order_id, line_no) REFERENCES orders(id, line_no) );
3. 导出 Parquet 复用
生成一次、导出成 Parquet,之后任何进程都能直接查询(这也是 PG 加速实验的载体):
COPY customers TO 'parquet/customers.parquet' (FORMAT PARQUET);
COPY products TO 'parquet/products.parquet' (FORMAT PARQUET);
COPY orders TO 'parquet/orders.parquet' (FORMAT PARQUET);
COPY order_items TO 'parquet/order_items.parquet' (FORMAT PARQUET);
之后直接查文件,无需建表:
SELECT city, count(*) AS n, round(sum(o.amount), 0) AS amt
FROM 'parquet/orders.parquet' o
JOIN 'parquet/customers.parquet' c ON o.customer_id = c.id
WHERE o.order_date >= DATE '2026-01-01'
GROUP BY city ORDER BY amt DESC LIMIT 3;
本机实测:100 万订单 → 14 MB 的 parquet(列式压缩),查询秒回。
4. TPC-H 基准数据(标准分析型数据集)
TPC-H 是什么: 业界标准分析型基准测试,由 TPC(事务处理性能委员会)制定, 定义了一套 8 张表的订单模型 Schema、数据生成器 dbgen 和 22 条标准查询。 数据库厂商都用它横向比性能(DuckDB 官网 benchmark 也用它),所以叫“基准数据”。
DuckDB 把 dbgen 内置成函数,一条命令就在当前库里直接生成 8 张表 (customer / orders / lineitem / part / partsupp / supplier / nation / region):
sf 是数据规模档位,各表行数按比例缩放:
| sf | 原始数据量 | lineitem 行数 |
|---|---|---|
| 0.01 | ~10MB | 6 万 |
| 0.1 | ~100MB | 60 万 |
| 1 | ~1GB | 600 万 |
原始数据量指未压缩的文本形式,落进 DuckDB 列式存储后体积小很多。
sf=0.1实测行数:orders 15 万、lineitem 60 万、customer 1.5 万。
和手搓数据(第 2 节)的区别: 手搓数据用 random() 均匀撒,分布太“平”; dbgen 按 TPC-H 规范生成,字段分布、键关联更像真实生产数据,性能实验的结果 更可信 —— 手搓数据用来跑通功能,TPC-H 用来测真实性能。
生成后直接用 TPC-H 标准查询验证(本机实测 sf=0.1,60 万行 lineitem,毫秒级):
-- 测试 1:TPC-H Q6 收入预测 —— 全表扫描 + 多条件过滤
-- 固定结果 revenue = 11803420.25(sf=0.1,DuckDB 1.5.5),可当数据完整性的回归校验
SELECT sum(l_extendedprice * l_discount) AS revenue
FROM lineitem
WHERE l_shipdate >= DATE '1994-01-01' AND l_shipdate < DATE '1995-01-01'
AND l_discount BETWEEN 0.06 - 0.01 AND 0.06 + 0.01
AND l_quantity < 24;
-- 测试 2:TPC-H Q3 运输优先级 —— 三表 JOIN + 聚合 + 排序
SELECT l_orderkey,
sum(l_extendedprice * (1 - l_discount)) AS revenue,
o_orderdate, o_shippriority
FROM customer, orders, lineitem
WHERE c_mktsegment = 'BUILDING'
AND c_custkey = o_custkey
AND l_orderkey = o_orderkey
AND o_orderdate < DATE '1995-03-15'
AND l_shipdate > DATE '1995-03-15'
GROUP BY l_orderkey, o_orderdate, o_shippriority
ORDER BY revenue DESC, o_orderdate
LIMIT 5;
想看耗时,把 .timer on 放查询前,用 stdin 喂给 CLI(-c 里不能用点命令):
5. 一个注意点:Parquet 的 min/max pruning
机制:Parquet 按 Row Group 存储 (DuckDB 写入时每组 122,880 行),每组头部带各列 min/max 统计。 查询带过滤条件时,引擎先比对各组的 min/max —— 某组范围与过滤条件完全不 重叠就整组跳过、不实际读数据,这就是 pruning(也叫 row group skipping)。它直接决定按时间过滤这类查询快不快:
SELECT row_group_id, path_in_schema, stats_min_value, stats_max_value
FROM parquet_metadata('parquet/orders.parquet')
WHERE path_in_schema = 'order_date' ORDER BY row_group_id LIMIT 3;
本实验的实测输出(每组都是 2024-08-11 ~ 2026-08-11):
│ 0 │ order_date │ 2024-08-11 │ 2026-08-11 │
│ 1 │ order_date │ 2024-08-11 │ 2026-08-11 │
│ 2 │ order_date │ 2024-08-11 │ 2026-08-11 │
⚠️ 注意: 本页
orders的日期用random()均匀撒,每个 Row Group 的 min/max 都覆盖全区间(实测输出可见)→ 按日期过滤时每组都可能命中,无法 pruning,等于全表扫描(最坏情况)。什么时候要紧: 用这份数据做性能实验(尤其按日期过滤,以及 PostgreSQL 加速查询 的对比实验)时,测出来的 是最坏情况数字,不代表真实负载 —— 真实生产数据(订单/日志/流水)是按时间 追加写入、天然有序的。跑通功能 / 验证正确性则无所谓,random 数据即可。
需要可剪枝的 Parquet 时,在导出那一刻按时间排序(Row Group 的 min/max 是写入时按数据流顺序算的,表本身的顺序无关):
实测:50 万行、5 个 Row Group,查最后一个月 —— 随机数据要读 5/5 组,排序后只读 1/5 组。 一句话:数据分布决定 pruning 效果,性能数字只有在数据长得像真实数据时才可信。
6. 参考链接
| 资源 | 链接 |
|---|---|
| generate_series 文档 | https://duckdb.org/docs/stable/sql/functions/utility |
| COPY 语句 | https://duckdb.org/docs/stable/sql/statements/copy |
| Parquet 元数据函数 | https://duckdb.org/docs/stable/data/parquet/metadata |
| TPC-H 数据生成(dbgen) | https://duckdb.org/docs/stable/guides/performance/benchmarking |
→ 下一站:PostgreSQL 加速查询
Backlinks (3)
flowchart LR
n0["AI"]
n1["Database"]
n2["Dev Tools"]
n3["Emoji"]
n4["Frontend"]
n5["Game Dev"]
n6["Collection"]
n7["Languages"]
n8["Maps"]
n9["Media"]
n10["Monitor"]
n11["Study Materials"]
n12["Plans"]
n13["Storage"]
n14["对象存储基本用法(Bucket / Object / 常用操作)"]
n15["挂载 Bucket 为本地文件系统(FUSE Mount)"]
n16["对象存储签名 URL(Signed URL)原理与实战"]
n17["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
n18["Prototypes"]
n19["Research"]
n20["Better Auth 源码阅读指南"]
n21["DuckDB 环境与基本使用"]
n22["DuckDB 实战研究"]
n23["DuckDB 模拟数据"]
n24["PostgreSQL 数据用 DuckDB 加速查询"]
n25["HK 角色阅读"]
n26["HK 台词阅读"]
n27["Hollow Knight 英语主题"]
n28["HK 物品阅读"]
n29["HK 地点阅读"]
n30["HK 世界观 / Lore 阅读"]
n31["英语学习 Dashboard"]
n32["English Scraps Archive"]
n33["English Scraps 使用指南"]
n34["Jellyfin 源码阅读指南"]
n35["Lux 资料整理"]
n36["Nest Commander 学习资料"]
n37["NestJS 源码阅读指南"]
n38["Protomaps 自建底图研究"]
n39["自制 PMTiles 地图(最简单例子)"]
n40["MapLibre 集成 Protomaps"]
n41["PMTiles 格式与工具链"]
n42["上海地区底图项目"]
n43["Redash 源码阅读指南"]
n44["Rust 学习计划"]
n45["shadcn/ui 进阶使用"]
n46["shadcn/ui 组件添加与基本使用"]
n47["shadcn/ui 实用研究(从环境到基本使用)"]
n48["shadcn/ui 环境与初始化"]
n49["TRIP 项目核心原理与代码阅读指南"]
n1 --> n22
n1 --> n24
n4 --> n47
n6 --> n0
n6 --> n1
n6 --> n2
n6 --> n3
n6 --> n4
n6 --> n5
n6 --> n7
n6 --> n8
n6 --> n9
n6 --> n10
n6 --> n11
n6 --> n12
n6 --> n13
n6 --> n19
n14 --> n16
n14 --> n17
n14 --> n18
n15 --> n14
n15 --> n16
n15 --> n17
n16 --> n18
n17 --> n14
n17 --> n16
n17 --> n18
n18 --> n14
n18 --> n15
n18 --> n16
n18 --> n17
n18 --> n38
n19 --> n20
n19 --> n22
n19 --> n31
n19 --> n34
n19 --> n35
n19 --> n36
n19 --> n37
n19 --> n38
n19 --> n43
n19 --> n44
n19 --> n47
n19 --> n49
n21 --> n23
n22 --> n1
n22 --> n19
n22 --> n21
n22 --> n23
n22 --> n24
n23 --> n21
n23 --> n24
n24 --> n22
n24 --> n23
n27 --> n25
n27 --> n26
n27 --> n28
n27 --> n29
n27 --> n30
n27 --> n33
n31 --> n27
n31 --> n32
n31 --> n33
n33 --> n31
n33 --> n32
n38 --> n8
n38 --> n39
n38 --> n40
n38 --> n41
n38 --> n42
n39 --> n41
n40 --> n18
n40 --> n42
n41 --> n39
n41 --> n42
n42 --> n40
n42 --> n41
n45 --> n46
n46 --> n45
n46 --> n48
n47 --> n4
n47 --> n19
n47 --> n45
n47 --> n46
n47 --> n48
n48 --> n46
click n0 "../../../../collection/ai/" "AI"
click n1 "../../../../collection/database/" "Database"
click n2 "../../../../collection/dev-tools/" "Dev Tools"
click n3 "../../../../collection/emoji/" "Emoji"
click n4 "../../../../collection/frontend/" "Frontend"
click n5 "../../../../collection/game-dev/" "Game Dev"
click n6 "../../../../collection/" "Collection"
click n7 "../../../../collection/languages/" "Languages"
click n8 "../../../../collection/maps/" "Maps"
click n9 "../../../../collection/media/" "Media"
click n10 "../../../../collection/monitor/" "Monitor"
click n11 "../../../../collection/reading/" "Study Materials"
click n12 "../../../../collection/scraps/plans/" "Plans"
click n13 "../../../../collection/storage/" "Storage"
click n14 "../../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
click n15 "../../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
click n16 "../../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
click n17 "../../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
click n18 "../../../../prototypes/" "Prototypes"
click n19 "../../../" "Research"
click n20 "../../better-auth/" "Better Auth 源码阅读指南"
click n21 "../basic-usage/" "DuckDB 环境与基本使用"
click n22 "../" "DuckDB 实战研究"
click n23 "./" "DuckDB 模拟数据"
click n24 "../postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
click n25 "../../english/hollow-knight/characters/" "HK 角色阅读"
click n26 "../../english/hollow-knight/dialogues/" "HK 台词阅读"
click n27 "../../english/hollow-knight/" "Hollow Knight 英语主题"
click n28 "../../english/hollow-knight/items/" "HK 物品阅读"
click n29 "../../english/hollow-knight/locations/" "HK 地点阅读"
click n30 "../../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
click n31 "../../english/" "英语学习 Dashboard"
click n32 "../../english/scraps/archive/" "English Scraps Archive"
click n33 "../../english/scraps/" "English Scraps 使用指南"
click n34 "../../jellyfin/" "Jellyfin 源码阅读指南"
click n35 "../../lux/" "Lux 资料整理"
click n36 "../../nest-commander/" "Nest Commander 学习资料"
click n37 "../../nestjs/" "NestJS 源码阅读指南"
click n38 "../../protomaps/" "Protomaps 自建底图研究"
click n39 "../../protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
click n40 "../../protomaps/maplibre/" "MapLibre 集成 Protomaps"
click n41 "../../protomaps/pmtiles/" "PMTiles 格式与工具链"
click n42 "../../protomaps/shanghai-map/" "上海地区底图项目"
click n43 "../../redash/" "Redash 源码阅读指南"
click n44 "../../rust/" "Rust 学习计划"
click n45 "../../shadcn-ui/advanced/" "shadcn/ui 进阶使用"
click n46 "../../shadcn-ui/components/" "shadcn/ui 组件添加与基本使用"
click n47 "../../shadcn-ui/" "shadcn/ui 实用研究(从环境到基本使用)"
click n48 "../../shadcn-ui/setup/" "shadcn/ui 环境与初始化"
click n49 "../../trip/" "TRIP 项目核心原理与代码阅读指南"