关于 db-seed
db-seed 是一项根据项目数据库结构生成可运行填充脚本的技能,适用于开发、演示和测试,并处理数据依赖与重复运行。
主要功能
- 01读取结构定义和表关系
- 02生成 Drizzle TypeScript 或原始 SQL
- 03按外键依赖安排插入顺序
- 04生成领域匹配的数据及边界样例
- 05支持可重复执行的填充策略
适用场景
- 01填充新的开发数据库
- 02为演示准备样例数据
- 03在测试中覆盖约束和边界情况
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill db-seed安装后在 Claude Code 中输入/db-seed确认已加载。
详细介绍
数据库种子数据生成器
生成种子脚本,使用符合领域特点的逼真示例数据填充数据库。读取你的架构并生成可直接运行的种子文件。
工作流
1. 查找架构
扫描项目以查找架构定义:
| 来源 | 位置模式 |
|---|---|
| Drizzle 架构 | src/db/schema.ts、src/schema/*.ts、db/schema.ts |
| D1 迁移 | drizzle/*.sql、migrations/*.sql |
| 原始 SQL | schema.sql、db/*.sql |
| Prisma | prisma/schema.prisma |
读取所有架构文件。梳理以下内容:
- 表及其列
- 数据类型和约束(NOT NULL、UNIQUE、DEFAULT)
- 外键关系(哪些表引用了哪些表)
- 以 TEXT 存储的 JSON 字段(D1/SQLite 中很常见)
2. 确定种子数据参数
询问用户:
| 参数 | 选项 | 默认值 |
|---|---|---|
| 用途 | dev、demo、testing | dev |
| 数量 | small(每张表 5-10 行)、medium(20-50 行)、large(100+ 行) | small |
| 领域背景 | “电商商店”、“SaaS 应用”、“博客”等 | 根据架构推断 |
| 输出格式 | TypeScript(Drizzle)、原始 SQL 或两者 | 与项目的 ORM 匹配 |
用途会影响数据质量:
- dev:数据多样,并包含一些边界情况(空字段、长字符串、Unicode)
- demo:精致的数据,适合截图和演示
- testing:系统化的数据,涵盖边界条件、重复项和特殊字符
3. 规划插入顺序
根据外键构建依赖关系图。先插入父表,再插入子表。
博客架构的示例顺序:
1. users (no dependencies)
2. categories (no dependencies)
3. posts (depends on users, categories)
4. comments (depends on users, posts)
5. tags (no dependencies)
6. post_tags (depends on posts, tags)
循环依赖:如果表 A 引用表 B,而表 B 也引用表 A,则使用可空外键并分两步插入(先插入 NULL,再执行 UPDATE)。
4. 生成逼真的数据
不要使用“test123”、“[email protected]”或“Lorem ipsum”等通用占位符。生成符合领域特点的数据。
数据生成模式(无需外部库)
姓名:使用硬编码的常见姓名列表。混合不同性别和文化背景的姓名。
const firstNames = ['Sarah', 'James', 'Priya', 'Mohammed', 'Emma', 'Wei', 'Carlos', 'Aisha'];
const lastNames = ['Chen', 'Smith', 'Patel', 'Garcia', 'Kim', 'O\'Brien', 'Nguyen', 'Wilson'];
电子邮件:根据姓名生成——[email protected]。使用 example.com 域名(由 RFC 2606 保留)。
日期:在合理的范围内生成。D1/SQLite 使用 ISO 8601 格式。
const randomDate = (daysBack: number) => {
const d = new Date();
d.setDate(d.getDate() - Math.floor(Math.random() * daysBack));
return d.toISOString();
};
ID:UUID 使用 crypto.randomUUID(),如果架构使用自增,则使用连续整数。
确定性种子数据:如需可复现的数据,请使用带种子的 PRNG:
function seededRandom(seed: number) {
return () => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
}
const rand = seededRandom(42); // Same seed = same data every time
价格/金额:使用合理的范围。(rand() * 900 + 100).toFixed(2) 可生成 $1-$10 范围内的值。
描述/内容:为每种内容类型撰写 3-5 个逼真的变体,并循环使用。不要生成听起来像 AI 写的文字——要像真实的用户数据。
5. 输出格式
TypeScript(Drizzle ORM)
// scripts/seed.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from '../src/db/schema';
export async function seed(db: ReturnType<typeof drizzle>) {
console.log('Seeding database...');
// Clear existing data (reverse dependency order)
await db.delete(schema.comments);
await db.delete(schema.posts);
await db.delete(schema.users);
// Insert users
const users = [
{ id: crypto.randomUUID(), name: 'Sarah Chen', email: '[email protected]', ... },
// ...
];
// D1 batch limit: 10 rows per INSERT
for (let i = 0; i < users.length; i += 10) {
await db.insert(schema.users).values(users.slice(i, i + 10));
}
// Insert posts (references users)
const posts = [
{ id: crypto.randomUUID(), userId: users[0].id, title: '...', ... },
// ...
];
for (let i = 0; i < posts.length; i += 10) {
await db.insert(schema.posts).values(posts.slice(i, i + 10));
}
console.log(`Seeded: ${users.length} users, ${posts.length} posts`);
}
运行命令:npx tsx scripts/seed.ts
对于 Cloudflare Workers,添加一个种子数据端点(在生产环境前移除):
app.post('/api/seed', async (c) => {
const db = drizzle(c.env.DB);
await seed(db);
return c.json({ ok: true });
});
原始 SQL(D1)
-- seed.sql
-- Run: npx wrangler d1 execute DB_NAME --local --file=./scripts/seed.sql
-- Clear existing (reverse order)
DELETE FROM comments;
DELETE FROM posts;
DELETE FROM users;
-- Users
INSERT INTO users (id, name, email, created_at) VALUES
('uuid-1', 'Sarah Chen', '[email protected]', '2025-01-15T10:30:00Z'),
('uuid-2', 'James Wilson', '[email protected]', '2025-02-01T14:22:00Z');
-- Posts (max 10 rows per INSERT for D1)
INSERT INTO posts (id, user_id, title, body, created_at) VALUES
('post-1', 'uuid-1', 'Getting Started', 'Welcome to...', '2025-03-01T09:00:00Z');
6. 幂等性
种子脚本必须支持安全重复运行:
// Option A: Delete-then-insert (simple, loses data)
await db.delete(schema.users);
await db.insert(schema.users).values(seedUsers);
// Option B: Upsert (preserves non-seed data)
for (const user of seedUsers) {
await db.insert(schema.users)
.values(user)
.onConflictDoUpdate({ target: schema.users.id, set: user });
}
dev/testing 默认使用选项 A,demo 默认使用选项 B(因为用户可能已经添加了自己的数据)。
D1 专属注意事项
| 注意事项 | 解决方案 |
|---|---|
| 每条 INSERT 最多约 10 行 | 分批插入,每批 10 行 |
| 不支持原生 BOOLEAN | 使用 INTEGER(0/1) |
| 不支持原生 DATETIME | 使用 TEXT 和 ISO 8601 字符串 |
| JSON 以 TEXT 存储 | 插入前使用 JSON.stringify() |
| 始终启用外键约束 | 先插入父表 |
| 绑定参数上限为 100 个 | 保持批次大小 × 列数小于 100 |
质量规则
- 符合领域特征 — 电商种子数据应包含名称听起来真实的商品和价格,而不是“商品 1”
- 让数据多样化 — 不要让每个用户都叫“John Smith”,也不要让每个价格都是“$9.99”
- 包含边界情况(用于测试种子数据)— 空字符串、超长文本、特殊字符、最大值
- 引用真实 ID — 外键必须指向实际插入的父行
- 打印已生成的种子数据 — 始终记录数量,让用户知道操作成功
- 记录运行命令 — 将其放在文件顶部的注释中