SQL 插入
Drizzle ORM 为你提供了最类似于 SQL 的方式将行插入数据库表。
插入一行
使用 Drizzle 插入数据非常简单,类似于 SQL。亲自体验:
await db.insert(users).values({ name: 'Andrew' });
insert into "users" ("name") values ("Andrew");
如果你需要特定表的插入类型,你可以使用 typeof usersTable.$inferInsert
语法。
type NewUser = typeof users.$inferInsert;
const insertUser = async (user: NewUser) => {
return db.insert(users).values(user);
}
const newUser: NewUser = { name: "Alef" };
await insertUser(newUser);
插入返回值
你可以在 PostgreSQL 和 SQLite 中插入一行并将其返回,如下所示:
await db.insert(users).values({ name: "Dan" }).returning();
// partial return
await db.insert(users).values({ name: "Partial Dan" }).returning({ insertedId: users.id });
插入 $returningId
使用 INSERT
后,MySQL 本身不再原生支持 RETURNING
。对于 primary keys
类型,只有一种方法可以访问 autoincrement
(或 serial
)类型的 insertId
和 affectedRows
字段。我们为你准备了一种使用 Drizzle 处理此类情况的自动化方法,并自动将所有插入的 ID 作为单独的对象接收。
import { boolean, int, text, mysqlTable } from 'drizzle-orm/mysql-core';
const usersTable = mysqlTable('users', {
id: int('id').primaryKey(),
name: text('name').notNull(),
verified: boolean('verified').notNull().default(false),
});
const result = await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]).$returningId();
// ^? { id: number }[]
此外,使用 Drizzle,你可以指定一个带有 $default
函数的 primary key
,该函数将在运行时生成自定义主键。我们还会在 $returningId()
调用中为你返回这些生成的密钥。
import { varchar, text, mysqlTable } from 'drizzle-orm/mysql-core';
import { createId } from '@paralleldrive/cuid2';
const usersTableDefFn = mysqlTable('users_default_fn', {
customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(createId),
name: text('name').notNull(),
});
const result = await db.insert(usersTableDefFn).values([{ name: 'John' }, { name: 'John1' }]).$returningId();
// ^? { customId: string }[]
如果没有主键,则此类查询的类型将为
{}[]
。
插入多行
await db.insert(users).values([{ name: 'Andrew' }, { name: 'Dan' }]);
Upsert 和冲突
Drizzle ORM 提供了用于处理更新插入和冲突的简单接口。
冲突时不执行任何操作
如果发生冲突,onConflictDoNothing
将取消插入:
await db.insert(users)
.values({ id: 1, name: 'John' })
.onConflictDoNothing();
// explicitly specify conflict target
await db.insert(users)
.values({ id: 1, name: 'John' })
.onConflictDoNothing({ target: users.id });
冲突时执行更新
如果发生冲突,onConflictDoUpdate
将更新行:
await db.insert(users)
.values({ id: 1, name: 'Dan' })
.onConflictDoUpdate({ target: users.id, set: { name: 'John' } });
where
子句
on conflict do update
可以在两个不同的地方包含 where
子句。 - 作为冲突目标的一部分(例如部分索引)或作为 update
子句的一部分:
insert into employees (employee_id, name)
values (123, 'John Doe')
on conflict (employee_id) where name <> 'John Doe'
do update set name = excluded.name
insert into employees (employee_id, name)
values (123, 'John Doe')
on conflict (employee_id) do update set name = excluded.name
where name <> 'John Doe';
要在 Drizzle 中指定这些条件,你可以使用 setWhere
和 targetWhere
子句:
await db.insert(employees)
.values({ employeeId: 123, name: 'John Doe' })
.onConflictDoUpdate({
target: employees.employeeId,
targetWhere: sql`name <> 'John Doe'`,
set: { name: sql`excluded.name` }
});
await db.insert(employees)
.values({ employeeId: 123, name: 'John Doe' })
.onConflictDoUpdate({
target: employees.employeeId,
set: { name: 'John Doe' },
setWhere: sql`name <> 'John Doe'`
});
使用复合索引或 onConflictDoUpdate
的复合主键进行更新插入:
await db.insert(users)
.values({ firstName: 'John', lastName: 'Doe' })
.onConflictDoUpdate({
target: [users.firstName, users.lastName],
set: { firstName: 'John1' }
});
重复键时更新
MySQL 支持 ON DUPLICATE KEY UPDATE
子句,而非 ON CONFLICT
子句。MySQL 将根据主键和唯一索引自动确定冲突目标,并在任何唯一索引发生冲突时更新行。
Drizzle 通过 onDuplicateKeyUpdate
方法支持此功能:
// Note that MySQL automatically determines targets based on the primary key and unique indexes
await db.insert(users)
.values({ id: 1, name: 'John' })
.onDuplicateKeyUpdate({ set: { name: 'John' } });
虽然 MySQL 不直接支持在冲突时不执行任何操作,但你可以通过将任何列的值设置为其自身来执行无操作,并达到相同的效果:
import { sql } from 'drizzle-orm';
await db.insert(users)
.values({ id: 1, name: 'John' })
.onDuplicateKeyUpdate({ set: { id: sql`id` } });
with insert
子句
使用 with
子句可以帮助你简化复杂查询,方法是将查询拆分为称为通用表表达式 (CTE) 的较小子查询:
const userCount = db.$with('user_count').as(
db.select({ value: sql`count(*)`.as('value') }).from(users)
);
const result = await db.with(userCount)
.insert(users)
.values([
{ username: 'user1', admin: sql`((select * from ${userCount}) = 0)` }
])
.returning({
admin: users.admin
});
with "user_count" as (select count(*) as "value" from "users")
insert into "users" ("username", "admin")
values ($1, ((select * from "user_count") = 0))
returning "admin"
插入到…select
正如 SQLite 文档中所述:
INSERT 语句的第二种形式包含一个 SELECT 语句,而不是 VALUES 子句。执行 SELECT 语句返回的每一行数据都会在表中插入一个新条目。如果指定了列列表,则 SELECT 结果中的列数必须与列列表中的项目数相同。否则,如果未指定列列表,则 SELECT 结果中的列数必须与表中的列数相同。任何 SELECT 语句,包括复合 SELECT 和带有 ORDER BY 和/或 LIMIT 子句的 SELECT 语句,都可以用于这种形式的 INSERT 语句中。
为避免解析歧义,SELECT 语句应始终包含 WHERE 子句,即使该子句只是 “WHERE true”,如果存在 upsert 子句。如果没有 WHERE 子句,解析器将无法确定标记 “ON” 是 SELECT 语句的连接约束的一部分,还是 upsert 子句的开头。
正如 PostgreSQL 文档中所述:
提供要插入行的查询(SELECT 语句)
正如 MySQL 文档所述:
使用 INSERT …SELECT,你可以快速将 SELECT 语句的结果中的多行插入到表中,该语句可以从一个或多个表中进行选择。
Drizzle 支持所有方言的当前语法,并且所有方言都共享相同的语法。让我们回顾一些常见的场景和 API 用法。在插入语句中使用 select 有几种方法,你可以选择自己喜欢的方法:
-
你可以在 select 函数中传递查询构建器。
-
你可以在回调中使用查询构建器。
-
你可以将 SQL 模板标签与任何要使用的自定义 select 查询一起传递。
const insertedEmployees = await db
.insert(employees)
.select(
db.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
)
.returning({
id: employees.id,
name: employees.name
});
const qb = new QueryBuilder();
await db.insert(employees).select(
qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);