7.1 Relational Queries 解决什么
手写 JOIN(第 6 章)能完成任何查询,但:
- 每条 JOIN 要手动指定 ON 条件——重复
- 一对多时要自己把平铺结果"拼回"嵌套对象——啰嗦
- 多层嵌套(用户→帖子→评论→回复)几乎写不动
Relational Queries 让你只声明关系一次,之后 db.query.users.findMany({ with: { posts: { with: { comments: true } } } }) 一句话取出树状数据——且只发 一条 SQL(用 JSON 聚合)。
7.2 定义 relations
在 schema 文件里,每张表写一个 relations():
import { relations } from "drizzle-orm";
import { pgTable, serial, text, integer } from "drizzle-orm/pg-core";
export const users = pgTable("users", { /* ... */ });
export const posts = pgTable("posts", {
id: serial().primaryKey(),
title: text().notNull(),
authorId: integer("author_id").notNull().references(() => users.id),
});
export const comments = pgTable("comments", {
id: serial().primaryKey(),
postId: integer("post_id").notNull().references(() => posts.id),
body: text().notNull(),
});
// 关系声明
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
comments: many(comments),
}));
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
}));
7.3 初始化 db.query
创建 db 实例时把 schema 整体传入:
import { drizzle } from "drizzle-orm/postgres-js";
import * as schema from "./schema";
export const db = drizzle(client, { schema });
// 现在 db.query.users / db.query.posts / db.query.comments 都可用
7.4 findMany / findFirst
// 所有用户
const list = await db.query.users.findMany();
// 取单条(limit 1)
const u = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, 1),
});
where 回调的第二参提供了所有操作符——零导入。
7.5 with:嵌套关系
const userWithPosts = await db.query.users.findFirst({
where: (u, { eq }) => eq(u.id, 1),
with: {
posts: true, // 取该用户所有 posts
},
});
// userWithPosts: { id, name, email, posts: Post[] } | undefined
嵌套多层
const data = await db.query.users.findMany({
with: {
posts: {
with: {
comments: true,
},
},
},
});
// [{ id, name, ..., posts: [{ id, title, ..., comments: [{...}] }] }]
7.6 columns:挑选/剔除字段
// 正选
const list = await db.query.users.findMany({
columns: { id: true, name: true },
});
// 反选(列出不要的)
const list = await db.query.users.findMany({
columns: { passwordHash: false }, // 除密码外所有字段
});
7.7 where / limit / orderBy 传给嵌套关系
const user = await db.query.users.findFirst({
where: (u, { eq }) => eq(u.id, 1),
with: {
posts: {
where: (p, { eq }) => eq(p.published, true),
orderBy: (p, { desc }) => [desc(p.createdAt)],
limit: 5,
columns: { id: true, title: true, createdAt: true },
with: {
comments: { limit: 3 },
},
},
},
});
7.8 多对多:join table
// tags (many) <-> posts (many) via posts_to_tags
export const tags = pgTable("tags", { id: serial().primaryKey(), name: text().notNull() });
export const postsToTags = pgTable("posts_to_tags", {
postId: integer("post_id").notNull().references(() => posts.id),
tagId: integer("tag_id").notNull().references(() => tags.id),
}, (t) => ({ pk: primaryKey({ columns: [t.postId, t.tagId] }) }));
export const postsRelations = relations(posts, ({ many }) => ({
postsToTags: many(postsToTags),
}));
export const tagsRelations = relations(tags, ({ many }) => ({
postsToTags: many(postsToTags),
}));
export const postsToTagsRelations = relations(postsToTags, ({ one }) => ({
post: one(posts, { fields: [postsToTags.postId], references: [posts.id] }),
tag: one(tags, { fields: [postsToTags.tagId], references: [tags.id] }),
}));
查询:
const data = await db.query.posts.findMany({
with: {
postsToTags: { with: { tag: true } },
},
});
// 每个 post 带 postsToTags: [{ tag: { id, name } }]
// 前端可以简单 .map(p => p.postsToTags.map(pt => pt.tag)) 摊平
7.9 单条 SQL:无 N+1
不同于 Prisma 的多次查询(早期版本)或 TypeORM 的 eager load,Drizzle 的 Relational Queries 只发一条 SQL——内部用 jsonb_agg / json_group_array 把关联数据打包成嵌套 JSON 返回。
性能示例:查 100 用户 + 各自 10 文章 + 各 5 评论:
- 朴素 ORM(N+1):1 + 100 + 1000 = 1101 条 SQL
- Drizzle Relational Query:1 条
7.10 何时用 Relational Queries,何时用手写 JOIN
| 场景 | 推荐 |
|---|---|
| 加载嵌套对象图(类 REST API 返回) | Relational Queries |
| 列表 + 计数(含聚合) | 手写 select + JOIN + groupBy |
| 跨表复杂过滤条件 | 手写 select + JOIN |
| 只关心"父关联子"的正常 CRUD | Relational Queries |
| 报表/分析类查询 | 手写 + sql 模板 |
7.11 小结
- 在 schema 里用
relations()声明 one/many 关系。 drizzle(client, { schema })才能开启db.query。findMany/findFirst+with可无限嵌套;columns精选字段。- 所有数据一条 SQL 返回——没 N+1,不需要 DataLoader。
- 复杂过滤/聚合仍然应当用手写 JOIN(第 6 章)。