Drizzle 总是显式列出 select
子句中的列,而不是使用 select *
。
这是内部要求,以保证查询结果中字段的顺序,通常也被认为是良好的实践。
SQL Select
Drizzle 为您提供了最像 SQL 的数据获取方式,同时保持类型安全和可组合性。它原生支持几乎所有方言的每个查询特性和功能,而且无论它目前不支持什么,用户都可以使用强大的 sql
操作符进行添加。
对于以下示例,我们假设您有一个名为 users
的表,其定义如下:
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
age: integer('age'),
});
基本查询
从表中选择所有行,包括所有列
const result = await db.select().from(users);
/*
{
id: number;
name: string;
age: number | null;
}[]
*/
select "id", "name", "age" from "users";
请注意,结果类型会根据表定义(包括列的可空性)自动推断。
部分查询
在某些情况下,您可能只想从表中选择部分列。您可以通过向 .select()
方法提供一个选择对象来实现:
const result = await db.select({
field1: users.id,
field2: users.name,
}).from(users);
const { field1, field2 } = result[0];
select "id", "name" from "users";
就像在 SQL 中一样,您可以使用任意表达式作为选择字段,而不仅仅是表列
const result = await db.select({
id: users.id,
lowerName: sql<string>`lower(${users.name})`,
}).from(users);
select "id", lower("name") from "users";
通过指定 sql<string>
,您是在告诉 Drizzle 该字段的**预期**类型是 string
。
如果您指定不正确(例如,对将作为字符串返回的字段使用 sql<number>
),则运行时值将与预期类型不匹配。Drizzle 无法根据提供的类型泛型执行任何类型转换,因为该信息在运行时不可用。
如果您需要对返回的值应用运行时转换,可以使用 .mapWith()
方法。
条件查询
您可以根据某些条件动态生成选择对象
async function selectUsers(withName: boolean) {
return db
.select({
id: users.id,
...(withName ? { name: users.name } : {}),
})
.from(users);
}
const users = await selectUsers(true);
DISTINCT 查询
您可以使用 .selectDistinct()
而不是 .select()
来仅检索数据集中的唯一行。
await db.selectDistinct().from(users).orderBy(usersTable.id, usersTable.name);
await db.selectDistinct({ id: users.id }).from(users).orderBy(usersTable.id);
select distinct "id", "name" from "users" order by "id", "name";
select distinct "id" from "users" order by "id";
在 PostgreSQL 中,您还可以使用 distinct on
子句来指定如何确定唯一行。
distinct on
子句仅在 PostgreSQL 中受支持。
await db.selectDistinctOn([users.id]).from(users).orderBy(users.id);
await db.selectDistinctOn([users.name], { name: users.name }).from(users).orderBy(users.name);
select distinct on ("id") "id", "name" from "users" order by "id";
select distinct on ("name") "name" from "users" order by "name";
高级查询
Drizzle API 由 TypeScript 提供支持,让您能够以各种灵活的方式构建查询。
高级部分查询的抢先预览,有关更详细的高级用法示例 - 请参阅我们的专用指南。
import { getTableColumns, sql } from 'drizzle-orm';
await db.select({
...getTableColumns(posts),
titleLength: sql<number>`length(${posts.title})`,
}).from(posts);
---
过滤器
您可以使用 .where()
方法中的过滤器操作符过滤查询结果。
import { eq, lt, gte, ne } from 'drizzle-orm';
await db.select().from(users).where(eq(users.id, 42));
await db.select().from(users).where(lt(users.id, 42));
await db.select().from(users).where(gte(users.id, 42));
await db.select().from(users).where(ne(users.id, 42));
...
select "id", "name", "age" from "users" where "id" = 42;
select "id", "name", "age" from "users" where "id" < 42;
select "id", "name", "age" from "users" where "id" >= 42;
select "id", "name", "age" from "users" where "id" <> 42;
所有过滤器操作符都使用 sql
函数实现。您可以自行使用它来编写任意 SQL 过滤器,或构建自己的操作符。为了获取灵感,您可以查看 Drizzle 提供的操作符是如何实现的。
import { sql } from 'drizzle-orm';
function equals42(col: Column) {
return sql`${col} = 42`;
}
await db.select().from(users).where(sql`${users.id} < 42`);
await db.select().from(users).where(sql`${users.id} = 42`);
await db.select().from(users).where(equals42(users.id));
await db.select().from(users).where(sql`${users.id} >= 42`);
await db.select().from(users).where(sql`${users.id} <> 42`);
await db.select().from(users).where(sql`lower(${users.name}) = 'aaron'`);
select "id", "name", "age" from "users" where 'id' < 42;
select "id", "name", "age" from "users" where 'id' = 42;
select "id", "name", "age" from "users" where 'id' = 42;
select "id", "name", "age" from "users" where 'id' >= 42;
select "id", "name", "age" from "users" where 'id' <> 42;
select "id", "name", "age" from "users" where lower("name") = 'aaron';
提供给过滤器操作符和 sql
函数的所有值都会自动参数化。例如,这个查询
await db.select().from(users).where(eq(users.id, 42));
将被转换为
select "id", "name", "age" from "users" where "id" = $1; -- params: [42]
使用 not
操作符反转条件
import { eq, not, sql } from 'drizzle-orm';
await db.select().from(users).where(not(eq(users.id, 42)));
await db.select().from(users).where(sql`not ${users.id} = 42`);
select "id", "name", "age" from "users" where not ("id" = 42);
select "id", "name", "age" from "users" where not ("id" = 42);
您可以安全地修改模式、重命名表和列,并且它们将自动反映在您的查询中,因为使用了模板插值,而不是像编写原始 SQL 时那样硬编码列或表名。
组合过滤器
您可以使用 and()
和 or()
操作符逻辑组合过滤器
import { eq, and, sql } from 'drizzle-orm';
await db.select().from(users).where(
and(
eq(users.id, 42),
eq(users.name, 'Dan')
)
);
await db.select().from(users).where(sql`${users.id} = 42 and ${users.name} = 'Dan'`);
select "id", "name", "age" from "users" where "id" = 42 and "name" = 'Dan';
select "id", "name", "age" from "users" where "id" = 42 and "name" = 'Dan';
import { eq, or, sql } from 'drizzle-orm';
await db.select().from(users).where(
or(
eq(users.id, 42),
eq(users.name, 'Dan')
)
);
await db.select().from(users).where(sql`${users.id} = 42 or ${users.name} = 'Dan'`);
select "id", "name", "age" from "users" where "id" = 42 or "name" = 'Dan';
select "id", "name", "age" from "users" where "id" = 42 or "name" = 'Dan';
高级过滤器
结合 TypeScript,Drizzle API 为您提供了强大而灵活的方式来组合查询中的过滤器。
条件过滤的抢先预览,有关更详细的高级用法示例 - 请参阅我们的专用指南。
const searchPosts = async (term?: string) => {
await db
.select()
.from(posts)
.where(term ? ilike(posts.title, term) : undefined);
};
await searchPosts();
await searchPosts('AI');
---
Limit & Offset
使用 .limit()
和 .offset()
向查询添加 limit
和 offset
子句——例如,实现分页。
await db.select().from(users).limit(10);
await db.select().from(users).limit(10).offset(10);
select "id", "name", "age" from "users" limit 10;
select "id", "name", "age" from "users" limit 10 offset 10;
排序
使用 .orderBy()
向查询添加 order by
子句,按指定字段对结果进行排序
import { asc, desc } from 'drizzle-orm';
await db.select().from(users).orderBy(users.name);
await db.select().from(users).orderBy(desc(users.name));
// order by multiple fields
await db.select().from(users).orderBy(users.name, users.name2);
await db.select().from(users).orderBy(asc(users.name), desc(users.name2));
select "id", "name", "age" from "users" order by "name";
select "id", "name", "age" from "users" order by "name" desc;
select "id", "name", "age" from "users" order by "name", "name2";
select "id", "name", "age" from "users" order by "name" asc, "name2" desc;
高级分页
Drizzle API 由 TypeScript 提供支持,让您能够实现所有可能的 SQL 分页和排序方法。
高级分页的抢先预览,有关更详细的高级用法示例 - 请参阅我们的专用limit offset 分页和基于游标的分页指南。
await db
.select()
.from(users)
.orderBy(asc(users.id)) // order by is mandatory
.limit(4) // the number of rows to return
.offset(4); // the number of rows to skip
---
WITH 子句
使用 with
子句可以通过将复杂查询拆分为称为公共表表达式 (CTE) 的更小查询来帮助您简化查询。
const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42)));
const result = await db.with(sq).select().from(sq);
with sq as (select "id", "name", "age" from "users" where "id" = 42)
select "id", "name", "age" from sq;
您还可以在 with
中提供 insert
、update
和 delete
语句
const sq = db.$with('sq').as(
db.insert(users).values({ name: 'John' }).returning(),
);
const result = await db.with(sq).select().from(sq);
with "sq" as (insert into "users" ("id", "name") values (default, 'John') returning "id", "name")
select "id", "name" from "sq"
const sq = db.$with('sq').as(
db.update(users).set({ age: 25 }).where(eq(users.name, 'John')).returning(),
);
const result = await db.with(sq).select().from(sq);
with "sq" as (update "users" set "age" = 25 where "users"."name" = 'John' returning "id", "name", "age")
select "id", "name", "age" from "sq"
const sq = db.$with('sq').as(
db.delete(users).where(eq(users.name, 'John')).returning(),
);
const result = await db.with(sq).select().from(sq);
with "sq" as (delete from "users" where "users"."name" = $1 returning "id", "name", "age")
select "id", "name", "age" from "sq"
要将任意 SQL 值作为 CTE 中的字段并将其引用到其他 CTE 或主查询中,您需要为其添加别名。
const sq = db.$with('sq').as(db.select({
name: sql<string>`upper(${users.name})`.as('name'),
})
.from(users));
const result = await db.with(sq).select({ name: sq.name }).from(sq);
如果您不提供别名,字段类型将变为 DrizzleTypeError
,并且您将无法在其他查询中引用它。如果您忽略类型错误并仍尝试使用该字段,您将收到运行时错误,因为没有办法在没有别名的情况下引用该字段。
从子查询中选择
就像在 SQL 中一样,您可以使用子查询 API 将查询嵌入到其他查询中。
const sq = db.select().from(users).where(eq(users.id, 42)).as('sq');
const result = await db.select().from(sq);
select "id", "name", "age" from (select "id", "name", "age" from "users" where "id" = 42) "sq";
子查询可以在任何可以使用表的地方使用,例如在连接中。
const sq = db.select().from(users).where(eq(users.id, 42)).as('sq');
const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id));
select "users"."id", "users"."name", "users"."age", "sq"."id", "sq"."name", "sq"."age" from "users"
left join (select "id", "name", "age" from "users" where "id" = 42) "sq"
on "users"."id" = "sq"."id";
---
聚合
使用 Drizzle,您可以使用 sum
、count
、avg
等函数进行聚合,通过 .groupBy()
和 .having()
进行分组和过滤,与您在原始 SQL 中所做的一样。
import { gt } from 'drizzle-orm';
await db.select({
age: users.age,
count: sql<number>`cast(count(${users.id}) as int)`,
})
.from(users)
.groupBy(users.age);
await db.select({
age: users.age,
count: sql<number>`cast(count(${users.id}) as int)`,
})
.from(users)
.groupBy(users.age)
.having(({ count }) => gt(count, 1));
select "age", cast(count("id") as int)
from "users"
group by "age";
select "age", cast(count("id") as int)
from "users"
group by "age"
having cast(count("id") as int) > 1;
cast(... as int)
是必要的,因为 count()
在 PostgreSQL 中返回 bigint
,在 MySQL 中返回 decimal
,它们被视为字符串值而不是数字。或者,您可以使用 .mapWith(Number)
在运行时将值转换为数字。
如果您需要计数聚合 - 我们建议使用我们的 $count
API。
聚合辅助函数
Drizzle 拥有一组封装的 sql
函数,因此您无需在应用程序中为常见情况编写 sql
模板
请记住,聚合函数通常与 SELECT 语句的 GROUP BY 子句一起使用。因此,如果您在单个查询中使用聚合函数和其他列进行选择,请务必使用 .groupBy
子句。
count
返回 expression
中的值数量。
import { count } from 'drizzle-orm'
await db.select({ value: count() }).from(users);
await db.select({ value: count(users.id) }).from(users);
select count("*") from "users";
select count("id") from "users";
// It's equivalent to writing
await db.select({
value: sql`count('*'))`.mapWith(Number)
}).from(users);
await db.select({
value: sql`count(${users.id})`.mapWith(Number)
}).from(users);
countDistinct
返回 expression
中非重复值的数量。
import { countDistinct } from 'drizzle-orm'
await db.select({ value: countDistinct(users.id) }).from(users);
select count(distinct "id") from "users";
// It's equivalent to writing
await db.select({
value: sql`count(${users.id})`.mapWith(Number)
}).from(users);
平均
返回 expression
中所有非空值的平均值(算术平均值)。
import { avg } from 'drizzle-orm'
await db.select({ value: avg(users.id) }).from(users);
select avg("id") from "users";
// It's equivalent to writing
await db.select({
value: sql`avg(${users.id})`.mapWith(String)
}).from(users);
avgDistinct
返回 expression
中所有非空值的平均值(算术平均值)。
import { avgDistinct } from 'drizzle-orm'
await db.select({ value: avgDistinct(users.id) }).from(users);
select avg(distinct "id") from "users";
// It's equivalent to writing
await db.select({
value: sql`avg(distinct ${users.id})`.mapWith(String)
}).from(users);
sum
返回 expression
中所有非空值的总和。
import { sum } from 'drizzle-orm'
await db.select({ value: sum(users.id) }).from(users);
select sum("id") from "users";
// It's equivalent to writing
await db.select({
value: sql`sum(${users.id})`.mapWith(String)
}).from(users);
sumDistinct
返回 expression
中所有非空且非重复值的总和。
import { sumDistinct } from 'drizzle-orm'
await db.select({ value: sumDistinct(users.id) }).from(users);
select sum(distinct "id") from "users";
// It's equivalent to writing
await db.select({
value: sql`sum(distinct ${users.id})`.mapWith(String)
}).from(users);
max
返回 expression
中的最大值。
import { max } from 'drizzle-orm'
await db.select({ value: max(users.id) }).from(users);
select max("id") from "users";
// It's equivalent to writing
await db.select({
value: sql`max(${expression})`.mapWith(users.id)
}).from(users);
min
返回 expression
中的最小值。
import { min } from 'drizzle-orm'
await db.select({ value: min(users.id) }).from(users);
select min("id") from "users";
// It's equivalent to writing
await db.select({
value: sql`min(${users.id})`.mapWith(users.id)
}).from(users);
一个更高级的例子
const orders = sqliteTable('order', {
id: integer('id').primaryKey(),
orderDate: integer('order_date', { mode: 'timestamp' }).notNull(),
requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(),
shippedDate: integer('shipped_date', { mode: 'timestamp' }),
shipVia: integer('ship_via').notNull(),
freight: numeric('freight').notNull(),
shipName: text('ship_name').notNull(),
shipCity: text('ship_city').notNull(),
shipRegion: text('ship_region'),
shipPostalCode: text('ship_postal_code'),
shipCountry: text('ship_country').notNull(),
customerId: text('customer_id').notNull(),
employeeId: integer('employee_id').notNull(),
});
const details = sqliteTable('order_detail', {
unitPrice: numeric('unit_price').notNull(),
quantity: integer('quantity').notNull(),
discount: numeric('discount').notNull(),
orderId: integer('order_id').notNull(),
productId: integer('product_id').notNull(),
});
db
.select({
id: orders.id,
shippedDate: orders.shippedDate,
shipName: orders.shipName,
shipCity: orders.shipCity,
shipCountry: orders.shipCountry,
productsCount: sql<number>`cast(count(${details.productId}) as int)`,
quantitySum: sql<number>`sum(${details.quantity})`,
totalPrice: sql<number>`sum(${details.quantity} * ${details.unitPrice})`,
})
.from(orders)
.leftJoin(details, eq(orders.id, details.orderId))
.groupBy(orders.id)
.orderBy(asc(orders.id))
.all();
$count
db.$count()
是 count(*)
的实用包装器,它是一个非常灵活的操作符,可以按原样或作为子查询使用,更多详细信息请参见我们的 GitHub 讨论。
const count = await db.$count(users);
// ^? number
const count = await db.$count(users, eq(users.name, "Dan")); // works with filters
select count(*) from "users";
select count(*) from "users" where "name" = 'Dan';
它在子查询中特别有用。
const users = await db.select({
...users,
postsCount: db.$count(posts, eq(posts.authorId, users.id)),
}).from(users);
与关系查询一起使用的示例
const users = await db.query.users.findMany({
extras: {
postsCount: db.$count(posts, eq(posts.authorId, users.id)),
},
});
---
迭代器
如果您需要从查询中返回大量行,并且不想将它们全部加载到内存中,您可以使用 .iterator()
将查询转换为异步迭代器。
const iterator = await db.select().from(users).iterator();
for await (const row of iterator) {
console.log(row);
}
它也适用于预处理语句。
const query = await db.select().from(users).prepare();
const iterator = await query.iterator();
for await (const row of iterator) {
console.log(row);
}
---
使用索引
USE INDEX
提示建议优化器在处理查询时考虑哪些索引。优化器不强制使用这些索引,但如果它们合适,会优先使用它们。
export const users = mysqlTable('users', {
id: int('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);
const usersTableNameIndex = index('users_name_index').on(users.name);
await db.select()
.from(users, { useIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));
您也可以在任何您想要的连接上使用此选项
await db.select()
.from(users)
.leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));
忽略索引
IGNORE INDEX
提示告诉优化器避免在查询中使用特定索引。MySQL 将考虑所有其他索引(如果有)或在必要时执行全表扫描。
export const users = mysqlTable('users', {
id: int('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);
const usersTableNameIndex = index('users_name_index').on(users.name);
await db.select()
.from(users, { ignoreIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));
您也可以在任何您想要的连接上使用此选项
await db.select()
.from(users)
.leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));
强制索引
FORCE INDEX
提示强制优化器在查询中使用指定的索引。如果指定的索引无法使用,MySQL 将不会回退到其他索引;它可能会转而进行全表扫描。
export const users = mysqlTable('users', {
id: int('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);
const usersTableNameIndex = index('users_name_index').on(users.name);
await db.select()
.from(users, { forceIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));
您也可以在任何您想要的连接上使用此选项
await db.select()
.from(users)
.leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
.where(eq(users.name, 'David'));