Chapter 07

Relational Queries

Drizzle 的"高层 ORM API"——一句 findMany 抓出嵌套关系,且全程单条 SQL,没有 N+1。

7.1 Relational Queries 解决什么

手写 JOIN(第 6 章)能完成任何查询,但:

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 评论:

7.10 何时用 Relational Queries,何时用手写 JOIN

场景推荐
加载嵌套对象图(类 REST API 返回)Relational Queries
列表 + 计数(含聚合)手写 select + JOIN + groupBy
跨表复杂过滤条件手写 select + JOIN
只关心"父关联子"的正常 CRUDRelational Queries
报表/分析类查询手写 + sql 模板

7.11 小结