此页面讲解的是 drizzle 版本 1.0.0-beta.1 及更高版本中可用的概念。
Drizzle 查询
npm i drizzle-orm@beta
npm i drizzle-kit@beta -D
Drizzle ORM 设计为在 SQL 之上提供一个薄的类型层。 我们坚信自己设计了从 TypeScript 操作 SQL 数据库的最佳方式,现在是让它变得更好的时候了。
关系查询旨在为你从 SQL 数据库查询嵌套的关系数据提供出色的开发者体验,避免多次连接和复杂的数据映射。
它是现有模式定义和查询构建器的一个扩展。 你可以根据需要选择使用它。 我们确保你同时拥有最佳的开发者体验和性能。
import { relations } from './schema';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ relations });
const result = await db.query.users.findMany({
with: {
posts: true
},
});[{
id: 10,
name: "Dan",
posts: [
{
id: 1,
content: "SQL 很棒",
authorId: 10,
},
{
id: 2,
content: "但看看关系查询",
authorId: 10,
}
]
}]关系查询是 Drizzle 原始 查询生成器 的一个扩展。
你需要在初始化 drizzle() 时提供全部的 tables 和 relations,然后使用 db.query API 即可。
drizzle 的导入路径取决于你使用的 数据库驱动。
import { relations } from './relations';
import { drizzle } from 'drizzle-orm/...';
const db = drizzle({ relations });
await db.query.users.findMany(...);Drizzle 提供 .findMany() 和 .findFirst() API。
查询多条
const users = await db.query.users.findMany();// 结果类型
const result: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
}[];查询首条
.findFirst() 会在查询中添加 limit 1 限制。
const user = await db.query.users.findFirst();// 结果类型
const result: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
};包含关联关系
with 操作符允许你组合多个关联表的数据,并正确聚合结果。
获取所有带评论的帖子:
const posts = await db.query.posts.findMany({
with: {
comments: true,
},
});获取首条带评论的帖子:
const post = await db.query.posts.findFirst({
with: {
comments: true,
},
});你可以根据需要链式嵌套 with 语句。
对任何嵌套的 with 查询,Drizzle 会使用 核心类型API 推断类型。
获取所有用户及其帖子,每个帖子包含评论列表:
const users = await db.query.users.findMany({
with: {
posts: {
with: {
comments: true,
},
},
},
});部分字段选择
columns 参数允许你包含或排除你想从数据库获取的列。
Drizzle 在查询层面执行部分选择,不会额外传输多余数据。
注意 Drizzle 输出的是单条 SQL 语句。
获取所有帖子,只取 id 和 content ,并包含 comments:
const posts = await db.query.posts.findMany({
columns: {
id: true,
content: true,
},
with: {
comments: true,
}
});获取所有帖子,不包含 content:
const posts = await db.query.posts.findMany({
columns: {
content: false,
},
});当同时有 true 和 false 的选择时,所有的 false 选项都会被忽略。
如果你包含了 name 字段,但排除了 id 字段,id 的排除就是多余的,
除了 name 外的其他字段都会被排除。
在同一个查询中同时排除和包含字段:
const users = await db.query.users.findMany({
columns: {
name: true,
id: false // 被忽略
},
});// 结果类型
const users: {
name: string;
};仅包含嵌套关联中的列:
const res = await db.query.users.findMany({
columns: {},
with: {
posts: true
}
});// 结果类型
const res: {
posts: {
id: number,
text: string
}
}[];嵌套部分字段选择
就像 部分选择,你也可以包含或排除嵌套关联的列:
const posts = await db.query.posts.findMany({
columns: {
id: true,
content: true,
},
with: {
comments: {
columns: {
authorId: false
}
}
}
});选择过滤
和我们的类 SQL 查询构建器一样, 关系查询 API 允许你使用我们 操作符 列表定义过滤和条件。
你可以从 drizzle-orm 中导入它们,或者使用回调语法:
const users = await db.query.users.findMany({
where: {
id: 1
}
});select * from users where id = 1查找 id=1 的帖子,并获取创建日期早于特定时间的评论:
await db.query.posts.findMany({
where: {
id: 1,
},
with: {
comments: {
where: {
createdAt: { lt: new Date() },
},
},
},
});所有过滤操作符列表
where: {
OR: [],
AND: [],
NOT: {},
RAW: (table) => sql`${table.id} = 1`,
// 通过关联过滤
[relation]: {},
// 通过字段过滤
[column]: {
OR: [],
AND: [],
NOT: {},
eq: 1,
ne: 1,
gt: 1,
gte: 1,
lt: 1,
lte: 1,
in: [1],
notIn: [1],
like: "",
ilike: "",
notLike: "",
notIlike: "",
isNull: true,
isNotNull: true,
arrayOverlaps: [1, 2],
arrayContained: [1, 2],
arrayContains: [1, 2]
},
},示例
const response = db.query.users.findMany({
where: {
age: 15,
},
});select "users"."id" as "id", "users"."name" as "name"
from "users"
where ("users"."age" = 15)关系过滤器
使用 Drizzle 关系查询,你不仅可以按查询的表过滤,还可以按查询中包含的任何表过滤。
示例: 获取 ID 大于 10 并且至少有一条内容以 “M” 开头的帖子的 users:
const usersWithPosts = await db.query.usersTable.findMany({
where: {
id: {
gt: 10
},
posts: {
content: {
like: 'M%'
}
}
},
});示例: 只获取至少有一条帖子的用户及帖子:
const response = db.query.users.findMany({
with: {
posts: true,
},
where: {
posts: true,
},
});限制与偏移
Drizzle ORM 为查询及嵌套实体提供了 limit 和 offset API。
查询 5 个帖子:
await db.query.posts.findMany({
limit: 5,
});查询帖子,每个帖子最多取 3 条评论:
await db.query.posts.findMany({
with: {
comments: {
limit: 3,
},
},
});offset 现在也可以用于关联表中!
await db.query.posts.findMany({
limit: 5,
offset: 2, // 正确 ✅
with: {
comments: {
offset: 3, // 正确 ✅
limit: 3,
},
},
});查询带评论的帖子,从第 5 条到第 10 条:
await db.query.posts.findMany({
with: {
comments: true,
},
limit: 5,
offset: 5,
});排序
Drizzle 提供关系查询构建器的排序 API。
你可以使用相同的 核心 API 排序,也可以用回调的 order by 操作符,无需导入。
当你在同一张表中使用多个 orderBy 语句时,它们会按照你添加的顺序依次出现在查询中。
await db.query.posts.findMany({
orderBy: {
id: "asc",
},
});同时使用升序和降序排序:
await db.query.posts.findMany({
orderBy: { id: "asc" },
with: {
comments: {
orderBy: { id: "desc" },
},
},
});你还可以在排序语句中使用自定义 sql:
await db.query.posts.findMany({
orderBy: (t) => sql`${t.id} asc`,
with: {
comments: {
orderBy: (t, { desc }) => desc(t.id),
},
},
});包含自定义字段
关系查询 API 允许你添加自定义附加字段。 当你需要检索数据并对其应用额外函数时非常有用。
目前 extras 中不支持聚合,请使用 核心查询 实现聚合。
import { sql } from 'drizzle-orm';
await db.query.users.findMany({
extras: {
loweredName: sql`lower(${users.name})`,
},
})await db.query.users.findMany({
extras: {
loweredName: (users, { sql }) => sql`lower(${users.name})`,
},
})lowerName 作为键会被包含在所有返回对象的字段中。
如果你对 extras 中的字段指定了 .as("<别名>") ,Drizzle 会忽略该操作。
用关系查询构建器获取所有用户及其群组,并包含 fullName 字段(由 firstName 和 lastName 拼接):
const res = await db.query.users.findMany({
extras: {
fullName: (users, { sql }) => sql<string>`concat(${users.name}, " ", ${users.name})`,
},
with: {
usersToGroups: {
with: {
group: true,
},
},
},
});// 结果类型
const res: {
id: number;
name: string;
verified: boolean;
invitedBy: number | null;
fullName: string;
usersToGroups: {
group: {
id: number;
name: string;
description: string | null;
};
}[];
}[];
获取所有带评论的帖子,并添加额外字段来计算帖子内容长度和每条评论内容长度:
const res = await db.query.posts.findMany({
extras: {
contentLength: (table, { sql }) => sql<number>`length(${table.content})`,
},
with: {
comments: {
extras: {
commentSize: (table, { sql }) => sql<number>`length(${table.content})`,
},
},
},
});// 结果类型
const res: {
id: number;
createdAt: Date;
content: string;
authorId: number | null;
contentLength: number;
comments: {
id: number;
createdAt: Date;
content: string;
creator: number | null;
postId: number | null;
commentSize: number;
}[];
};包含子查询
你也可以在关系查询中使用子查询,以利用自定义 SQL 语法的强大功能。
获取用户及其帖子,同时获取每位用户帖子总数
import { posts } from './schema';
import { eq } from 'drizzle-orm';
await db.query.users.findMany({
with: {
posts: true
},
extras: {
totalPostsCount: (table) => db.$count(posts, eq(posts.authorId, table.id)),
}
});select "d0"."id" as "id", "d0"."name" as "name", "posts"."r" as "posts",
((select count(*) from "posts" where "posts"."author_id" = "d0"."id")) as "totalPostsCount"
from "users" as "d0"
left join lateral(
select coalesce(json_agg(row_to_json("t".*)), '[]') as "r"
from (select "d1"."id" as "id", "d1"."content" as "content", "d1"."author_id" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."author_id") as "t"
) as "posts" on true预处理语句
预处理语句旨在大幅提升查询性能 — 详情见此处
本节介绍如何使用 Drizzle 关系查询构建器定义占位符并执行预处理语句。
where 中的占位符
const prepared = db.query.users.findMany({
where: { id: { eq: sql.placeholder("id") } },
with: {
posts: {
where: { id: 1 },
},
},
}).prepare("query_name");
const usersWithPosts = await prepared.execute({ id: 1 });limit 中的占位符
const prepared = db.query.users.findMany({
with: {
posts: {
limit: sql.placeholder("limit"),
},
},
}).prepare("query_name");
const usersWithPosts = await prepared.execute({ limit: 1 });offset 中的占位符
const prepared = db.query.users.findMany({
offset: sql.placeholder('offset'),
with: {
posts: true,
},
}).prepare('query_name');
const usersWithPosts = await prepared.execute({ offset: 1 });多个占位符
const prepared = 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 usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 });