| name | drizzle-v1 |
| description | Drizzle ORM v1 关系查询指南。当需要定义 Relations v2、编写关系查询、使用 through 多对多、预定义过滤器、或从旧版 Drizzle 迁移时使用 |
| argument-hint | 关系定义/查询写法/迁移指导 |
Drizzle ORM v1 (Relations Query v2) 指南
版本信息
- 当前版本:
drizzle-orm@1.0.0-beta.21(已进入 RC 阶段)
- 官方文档: 所有 drizzle 官方文档已更新为 v1 写法
- 关键变化: Relations 定义方式、查询 API、多对多 through 支持
核心概念
drizzle() 初始化
传入 relations 而非 schema:
import { drizzle } from "drizzle-orm/postgres-js";
import { relations } from "./relations";
const db = drizzle({
client: getQueryClient(),
relations,
casing: "snake_case",
});
Relations 定义
基本结构
使用 defineRelations 在一处定义所有关系:
import { defineRelations } from "drizzle-orm";
import * as schema from "@/db/schema";
export const relations = defineRelations(schema, (r) => ({
users: {
posts: r.many.posts(),
},
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
},
}));
本项目的分片模式
本项目使用 defineRelationsPart + spread 方式拆分关系定义:
import { defineRelations } from "drizzle-orm";
import * as schema from "@/db/schema";
import { userRolesRelations } from "./admin/user-roles";
export const relations = defineRelations(schema, (r) => ({
...userRolesRelations(r),
}));
import type { ExtractTablesFromSchema, RelationsBuilder } from "drizzle-orm";
import type * as schema from "@/db/schema";
type Schema = ExtractTablesFromSchema<typeof schema>;
export const userRolesRelations = (r: RelationsBuilder<Schema>) => ({
systemUsers: {
roles: r.many.systemRoles({
from: r.systemUsers.id.through(r.systemUserRoles.userId),
to: r.systemRoles.id.through(r.systemUserRoles.roleId),
}),
enabledRoles: r.many.systemRoles({
from: r.systemUsers.id.through(r.systemUserRoles.userId),
to: r.systemRoles.id.through(r.systemUserRoles.roleId),
where: { status: Status.ENABLED },
}),
},
systemRoles: {
users: r.many.systemUsers(),
},
});
关键模式:
- 类型使用
ExtractTablesFromSchema<typeof schema> + RelationsBuilder<Schema>
- 返回对象直接 spread 到
defineRelations 中
- 按
src/db/relations/{tier}/{feature}.ts 组织文件
一对一 / 一对多
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
},
users: {
posts: r.many.posts({
from: r.users.id,
to: r.posts.authorId,
}),
},
多对多 (through)
使用 .through() 指定连接表,无需手动查询连接表再映射:
users: {
groups: r.many.groups({
from: r.users.id.through(r.usersToGroups.userId),
to: r.groups.id.through(r.usersToGroups.groupId),
}),
},
groups: {
participants: r.many.users(),
},
预定义过滤器 (where)
在关系定义中预设过滤条件,查询时直接使用:
systemUsers: {
enabledRoles: r.many.systemRoles({
from: r.systemUsers.id.through(r.systemUserRoles.userId),
to: r.systemRoles.id.through(r.systemUserRoles.roleId),
where: { status: Status.ENABLED },
}),
},
optional 选项
optional: false 使关系成为必填(TypeScript 类型非 nullable):
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
optional: false,
}),
},
alias(替代旧版 relationName)
自引用或同表多关系时使用 alias 区分:
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
alias: "author_post",
}),
reviewer: r.one.users({
from: r.posts.reviewerId,
to: r.users.id,
alias: "reviewer_post",
}),
},
查询 API
where(对象语法)
db.query.users.findFirst({
where: { id: userId },
});
db.query.users.findMany({
where: { status: Status.ENABLED, username: "admin" },
});
db.query.users.findMany({
where: {
id: { gt: 10 },
name: { like: "M%" },
},
});
db.query.users.findMany({
where: {
id: { gt: 10 },
posts: {
content: { like: "M%" },
},
},
});
db.query.users.findMany({
with: { posts: true },
where: { posts: true },
});
where 完整操作符参考
where: {
OR: [],
AND: [],
NOT: {},
RAW: (table) => sql`${table.id} = 1`,
[relation]: {},
[column]: {
eq: 1,
ne: 1,
gt: 1,
gte: 1,
lt: 1,
lte: 1,
in: [1, 2],
notIn: [1, 2],
like: "M%",
ilike: "m%",
notLike: "M%",
notIlike: "m%",
isNull: true,
isNotNull: true,
arrayOverlaps: [1, 2],
arrayContained: [1, 2],
arrayContains: [1, 2],
OR: [],
AND: [],
NOT: {},
},
}
orderBy(对象语法)
db.query.users.findMany({
orderBy: { id: "asc" },
});
db.query.users.findMany({
orderBy: { createdAt: "desc", name: "asc" },
});
db.query.posts.findMany({
orderBy: (t) => sql`${t.id} asc`,
});
db.query.posts.findMany({
orderBy: { id: "asc" },
with: {
comments: {
orderBy: { id: "desc" },
},
},
});
with(关系加载)
db.query.users.findFirst({
where: { id: userId },
with: { roles: true },
});
db.query.users.findFirst({
where: { id: userId },
with: {
roles: {
columns: { id: true, name: true },
},
},
});
db.query.users.findFirst({
where: { id: userId },
with: { enabledRoles: { columns: { id: true } } },
});
columns(选择列)
db.query.users.findFirst({
where: { id: userId },
columns: { id: true, username: true, avatar: true },
});
offset(支持关系对象)
db.query.posts.findMany({
limit: 5,
offset: 2,
with: {
comments: {
offset: 3,
limit: 3,
},
},
});
extras(自定义计算字段)
在查询中添加自定义 SQL 计算字段:
import { sql } from "drizzle-orm";
db.query.users.findMany({
extras: {
loweredName: sql`lower(${users.name})`,
},
});
db.query.users.findMany({
extras: {
loweredName: (users, { sql }) => sql`lower(${users.name})`,
},
});
db.query.posts.findMany({
extras: {
contentLength: (table, { sql }) => sql<number>`length(${table.content})`,
},
with: {
comments: {
extras: {
commentSize: (table, { sql }) => sql<number>`length(${table.content})`,
},
},
},
});
注意:extras 目前不支持聚合函数,需要聚合请使用 core queries。
extras 子查询
import { posts } from "./schema";
import { eq } from "drizzle-orm";
db.query.users.findMany({
with: { posts: true },
extras: {
totalPostsCount: (table) => db.$count(posts, eq(posts.authorId, table.id)),
},
});
Prepared Statements(预编译查询)
使用 placeholder 提升重复查询性能:
import { sql } from "drizzle-orm";
const prepared = db.query.users.findMany({
where: { id: { eq: sql.placeholder("id") } },
with: {
posts: {
where: { id: 1 },
},
},
}).prepare("query_name");
const result = await prepared.execute({ id: 1 });
const prepared2 = db.query.users.findMany({
limit: sql.placeholder("uLimit"),
offset: sql.placeholder("uOffset"),
where: {
OR: [{ id: { eq: sql.placeholder("id") } }, { id: 3 }],
},
with: {
posts: {
where: { id: { eq: sql.placeholder("pid") } },
limit: sql.placeholder("pLimit"),
},
},
}).prepare("query_name");
const result2 = await prepared2.execute({
pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6,
});
defineRelationsPart 规则
使用 defineRelationsPart 拆分关系定义时的重要规则:
规则 1:spread 时主 relations 必须在前:
const db = drizzle(url, { relations: { ...relations, ...part } })
const db = drizzle(url, { relations: { ...part, ...relations } })
规则 2:必须有一个 main relations(使用 defineRelations),让 drizzle 能推断所有表。如果只想用 parts,可以创建一个空的 main:
export const mainPart = defineRelationsPart(schema);
多对多查询对比
v1 之前(绕过连接表)
const response = await db.query.users.findMany({
with: {
usersToGroups: {
columns: {},
with: { group: true },
},
},
});
v1 新版(through 直查)
const response = await db.query.users.findMany({
with: { groups: true },
});
新增 Relations 文件步骤
- 创建关系文件
src/db/relations/{tier}/{feature}.ts
- 定义类型化函数,返回关系对象
- 在
src/db/relations/index.ts 注册:spread 到 defineRelations 中
import type { ExtractTablesFromSchema, RelationsBuilder } from "drizzle-orm";
import type * as schema from "@/db/schema";
type Schema = ExtractTablesFromSchema<typeof schema>;
export const {feature}Relations = (r: RelationsBuilder<Schema>) => ({
{parentTable}: {
{relName}: r.many.{childTable}({
from: r.{parentTable}.id,
to: r.{childTable}.{parentTable}Id,
}),
},
{childTable}: {
{parentRef}: r.one.{parentTable}({
from: r.{childTable}.{parentTable}Id,
to: r.{parentTable}.id,
}),
},
});
import { {feature}Relations } from "./{tier}/{feature}";
export const relations = defineRelations(schema, (r) => ({
...userRolesRelations(r),
...{feature}Relations(r),
}));
注意:同一个表的关系在多个 part 中定义时,后 spread 的会覆盖前面同名的 key。确保不同 part 中同一表的关系 key 名不冲突,或在 index.ts 中手动合并。
从旧版迁移速查
| v1 旧版 | v1 新版 (Relations v2) |
|---|
import { relations } from "drizzle-orm" | import { relations } from "drizzle-orm/_relations" ← 旧版移到这 |
fields: [posts.authorId] | from: r.posts.authorId |
references: [users.id] | to: r.users.id |
relationName: "xxx" | alias: "xxx" |
每表单独 relations() | 统一 defineRelations() |
db.query.xxx (旧语法) | db._query.xxx (可继续用旧语法) |
where: (t, { eq }) => eq(t.id, 1) | where: { id: 1 } |
orderBy: (t, { asc }) => [asc(t.id)] | orderBy: { id: "asc" } |
drizzle(url, { schema }) | drizzle(url, { relations }) |
MySQL mode: "planetscale" | 不再需要 mode |
渐进迁移策略
如果需要逐步迁移而非一次性切换:
- 将旧版
import { relations } from "drizzle-orm" 改为 import { relations } from "drizzle-orm/_relations"
- 旧查询用
db._query.xxx 替代 db.query.xxx
- 新查询用
db.query.xxx(新语法)
- 逐个迁移旧查询
drizzle-kit pull 自动迁移
pnpm drizzle-kit pull
会在 drizzle/relations.ts 生成新语法的关系定义,可直接复制到项目的 src/db/relations/ 中使用。注意修改导入路径。
常见陷阱
- 不要混用新旧 relations 定义:
defineRelations (v2) 和旧版 relations() (v1) 不兼容
- from/to 不再用数组:单列时直接传值
from: r.posts.authorId,多列时才用数组 from: [r.posts.a, r.posts.b]
- spread 覆盖:多个 part spread 时,同表同 key 会被后者覆盖
- through 需要连接表已在 schema 中定义:
r.usersToGroups.userId 前提是 usersToGroups 表已导出
- where 预定义过滤器只能过滤目标表:
where 子句只能包含 to 端表的列
- extras 不支持
.as() 别名:drizzle 会忽略 extras 字段上的 .as("<alias>")
升级步骤(从旧版到 v1 RC)
- 运行
pnpm drizzle-kit up:更新 migrations 文件夹结构(移除 journal.json,按文件夹分组)
- 更新 validator 包导入:
drizzle-zod → drizzle-orm/zod
drizzle-valibot → drizzle-orm/valibot
drizzle-typebox → drizzle-orm/typebox
drizzle-arktype → drizzle-orm/arktype
- 迁移 Relations 和查询:参照本文档的迁移速查表
官方文档参考