CRUD
本页介绍如何使用你生成的 Prisma Client API 执行 CRUD 操作。CRUD 是以下英文单词的首字母缩写:
请参考 Prisma Client API 参考文档,获取每种方法的详细解释。
示例 schema
所有示例都基于以下 schema
展开查看示例 schema
- 关系型数据库
- MongoDB
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model ExtendedProfile {
id Int @id @default(autoincrement())
biography String
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
profileViews Int @default(0)
role Role @default(USER)
coinflips Boolean[]
posts Post[]
profile ExtendedProfile?
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(true)
author User @relation(fields: [authorId], references: [id])
authorId Int
comments Json?
views Int @default(0)
likes Int @default(0)
categories Category[]
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model ExtendedProfile {
id String @id @default(auto()) @map("_id") @db.ObjectId
biography String
user User @relation(fields: [userId], references: [id])
userId String @unique @db.ObjectId
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
email String @unique
profileViews Int @default(0)
role Role @default(USER)
coinflips Boolean[]
posts Post[]
profile ExtendedProfile?
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
published Boolean @default(true)
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
comments Json?
views Int @default(0)
likes Int @default(0)
categories Category[]
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
对于关系型数据库,使用 db push
命令将示例 schema 推送到你自己的数据库
npx prisma db push
对于 MongoDB,请确保你的数据形状统一,并与 Prisma schema 中定义的模型匹配。
创建
创建单个记录
以下查询创建 (create()
) 一个包含两个字段的单个用户
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Elsa Prisma',
},
})
用户的 id
是自动生成的,你的 schema 决定了 哪些字段是必需的。
使用生成的类型创建单个记录
以下示例产生相同的结果,但会在 create()
查询的上下文之外创建一个名为 user
的 UserCreateInput
变量。在完成一个简单的检查(“此 create()
查询中应包含 posts 吗?”)后,user
变量被传递到查询中
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
let includePosts: boolean = false
let user: Prisma.UserCreateInput
// Check if posts should be included in the query
if (includePosts) {
user = {
email: '[email protected]',
name: 'Elsa Prisma',
posts: {
create: {
title: 'Include this post!',
},
},
}
} else {
user = {
email: '[email protected]',
name: 'Elsa Prisma',
}
}
// Pass 'user' object into query
const createUser = await prisma.user.create({ data: user })
}
main()
有关使用生成的类型的更多信息,请参阅:生成的类型。
创建多个记录
Prisma Client 在 2.20.0 及更高版本中以 GA 功能支持批量插入。
以下 createMany()
查询创建多个用户,并跳过任何重复项(email
必须是唯一的)
const createMany = await prisma.user.createMany({
data: [
{ name: 'Bob', email: '[email protected]' },
{ name: 'Bobo', email: '[email protected]' }, // Duplicate unique key!
{ name: 'Yewande', email: '[email protected]' },
{ name: 'Angelique', email: '[email protected]' },
],
skipDuplicates: true, // Skip 'Bobo'
})
{
count: 3
}
注意:当使用 MongoDB、SQLServer 或 SQLite 时,不支持 skipDuplicates
。
createMany()
使用单个带有多个值的 INSERT INTO
语句,这通常比每行单独的 INSERT
更有效率
BEGIN
INSERT INTO "public"."User" ("id","name","email","profileViews","role","coinflips","testing","city","country") VALUES (DEFAULT,$1,$2,$3,$4,DEFAULT,DEFAULT,DEFAULT,$5), (DEFAULT,$6,$7,$8,$9,DEFAULT,DEFAULT,DEFAULT,$10), (DEFAULT,$11,$12,$13,$14,DEFAULT,DEFAULT,DEFAULT,$15), (DEFAULT,$16,$17,$18,$19,DEFAULT,DEFAULT,DEFAULT,$20) ON CONFLICT DO NOTHING
COMMIT
SELECT "public"."User"."country", "public"."User"."city", "public"."User"."email", SUM("public"."User"."profileViews"), COUNT(*) FROM "public"."User" WHERE 1=1 GROUP BY "public"."User"."country", "public"."User"."city", "public"."User"."email" HAVING AVG("public"."User"."profileViews") >= $1 ORDER BY "public"."User"."country" ASC OFFSET $2
注意:在
$transaction
中的多个create()
语句会导致多个INSERT
语句。
以下视频演示了如何使用 createMany()
和 faker.js 来使用示例数据填充数据库
创建记录并连接或创建相关记录
有关同时创建一个记录和一个或多个相关记录的信息,请参阅处理关系 > 嵌套写入。
创建并返回多个记录
此功能在 Prisma ORM 5.14.0 及更高版本中适用于 PostgreSQL、CockroachDB 和 SQLite。
你可以使用 createManyAndReturn()
以创建多个记录并返回结果对象。
const users = await prisma.user.createManyAndReturn({
data: [
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' },
],
})
当使用 createManyAndReturn()
时,relationLoadStrategy: join
不可用。
读取
通过 ID 或唯一标识符获取记录
以下查询通过唯一标识符或 ID 返回单个记录 (findUnique()
)
// By unique identifier
const user = await prisma.user.findUnique({
where: {
email: '[email protected]',
},
})
// By ID
const user = await prisma.user.findUnique({
where: {
id: 99,
},
})
如果你正在使用 MongoDB 连接器并且你的底层 ID 类型是 ObjectId
,你可以使用该 ObjectId
的字符串表示形式
// By ID
const user = await prisma.user.findUnique({
where: {
id: '60d5922d00581b8f0062e3a8',
},
})
获取所有记录
以下 findMany()
查询返回所有 User
记录
const users = await prisma.user.findMany()
你还可以对结果进行分页。
获取符合特定条件的第一条记录
以下 findFirst()
查询返回最近创建的,且至少有一篇帖子点赞数超过 100 的用户
- 按降序 ID (最大 ID 在前) 对用户进行排序 - 最大的 ID 是最新的
- 返回降序排列的第一个用户,该用户至少有一篇帖子点赞数超过 100
const findUser = await prisma.user.findFirst({
where: {
posts: {
some: {
likes: {
gt: 100,
},
},
},
},
orderBy: {
id: 'desc',
},
})
获取过滤后的记录列表
Prisma Client 支持对记录字段和相关记录字段进行过滤。
按单个字段值过滤
以下查询返回 email
以 prisma.io
结尾的所有 User
记录
const users = await prisma.user.findMany({
where: {
email: {
endsWith: 'prisma.io',
},
},
})
按多个字段值过滤
以下查询使用运算符组合来返回名字以 E
开头的用户或至少有 1 次个人资料浏览量的管理员
const users = await prisma.user.findMany({
where: {
OR: [
{
name: {
startsWith: 'E',
},
},
{
AND: {
profileViews: {
gt: 0,
},
role: {
equals: 'ADMIN',
},
},
},
],
},
})
按相关记录字段值过滤
以下查询返回 email
以 prisma.io
结尾,并且至少有一篇 (some
) 未发布的帖子的用户
const users = await prisma.user.findMany({
where: {
email: {
endsWith: 'prisma.io',
},
posts: {
some: {
published: false,
},
},
},
})
有关按相关字段值过滤的更多示例,请参阅处理关系。
选择字段子集
以下 findUnique()
查询使用 select
返回特定 User
记录的 email
和 name
字段
const user = await prisma.user.findUnique({
where: {
email: '[email protected]',
},
select: {
email: true,
name: true,
},
})
有关包含关系的更多信息,请参考
选择相关记录字段的子集
以下查询使用嵌套的 select
返回
- 用户的
email
- 每篇帖子的
likes
字段
const user = await prisma.user.findUnique({
where: {
email: '[email protected]',
},
select: {
email: true,
posts: {
select: {
likes: true,
},
},
},
})
有关包含关系的更多信息,请参阅选择字段和包含关系。
选择不同的字段值
有关选择不同的字段值的信息,请参阅选择不同的值。
包含相关记录
以下查询返回所有 ADMIN
用户,并在结果中包含每个用户的帖子
const users = await prisma.user.findMany({
where: {
role: 'ADMIN',
},
include: {
posts: true,
},
})
有关包含关系的更多信息,请参阅选择字段和包含关系。
包含过滤后的关系列表
请参阅处理关系,了解如何将 include
和 where
结合使用以获得过滤后的关系列表 - 例如,仅包含用户已发布的帖子。
更新
更新单个记录
以下查询使用 update()
按 email
查找并更新单个 User
记录
const updateUser = await prisma.user.update({
where: {
email: '[email protected]',
},
data: {
name: 'Viola the Magnificent',
},
})
更新多个记录
以下查询使用 updateMany()
更新所有包含 prisma.io
的 User
记录
const updateUsers = await prisma.user.updateMany({
where: {
email: {
contains: 'prisma.io',
},
},
data: {
role: 'ADMIN',
},
})
更新并返回多个记录
此功能在 Prisma ORM 6.2.0 及更高版本中适用于 PostgreSQL、CockroachDB 和 SQLite。
你可以使用 updateManyAndReturn()
以更新多个记录并返回结果对象。
const users = await prisma.user.updateManyAndReturn({
where: {
email: {
contains: 'prisma.io',
}
},
data: {
role: 'ADMIN'
}
})
当使用 updateManyAndReturn()
时,relationLoadStrategy: join
不可用。
更新或创建记录
以下查询使用 upsert()
更新具有特定 email 地址的 User
记录,如果该 User
记录不存在,则创建它
const upsertUser = await prisma.user.upsert({
where: {
email: '[email protected]',
},
update: {
name: 'Viola the Magnificent',
},
create: {
email: '[email protected]',
name: 'Viola the Magnificent',
},
})
从 4.6.0 版本开始,Prisma Client 在可能的情况下使用数据库原生 SQL 命令执行 upsert。了解更多。
Prisma Client 没有 findOrCreate()
查询。你可以使用 upsert()
作为一种变通方法。要使 upsert()
的行为类似于 findOrCreate()
方法,请为 upsert()
提供一个空的 update
参数。
使用 upsert()
作为 findOrCreate()
的变通方法的一个限制是,upsert()
只接受 where
条件中唯一的模型字段。因此,如果 where
条件包含非唯一字段,则无法使用 upsert()
来模拟 findOrCreate()
。
更新数字字段
使用原子数字操作根据数字字段的当前值更新它 - 例如,递增或乘法。以下查询将 views
和 likes
字段递增 1
const updatePosts = await prisma.post.updateMany({
data: {
views: {
increment: 1,
},
likes: {
increment: 1,
},
},
})
连接和断开相关记录
有关断开连接 (disconnect
) 和连接 (connect
) 相关记录的信息,请参考处理关系。
删除
删除单个记录
以下查询使用 delete()
删除单个 User
记录
const deleteUser = await prisma.user.delete({
where: {
email: '[email protected]',
},
})
尝试删除具有一篇或多篇帖子的用户会导致错误,因为每篇 Post
都需要一个作者 - 请参阅级联删除。
删除多个记录
以下查询使用 deleteMany()
删除 email
包含 prisma.io
的所有 User
记录
const deleteUsers = await prisma.user.deleteMany({
where: {
email: {
contains: 'prisma.io',
},
},
})
尝试删除具有一篇或多篇帖子的用户会导致错误,因为每篇 Post
都需要一个作者 - 请参阅级联删除。
删除所有记录
以下查询使用 deleteMany()
删除所有 User
记录
const deleteUsers = await prisma.user.deleteMany({})
请注意,如果用户有任何相关记录(例如帖子),此查询将失败。在这种情况下,你需要先删除相关记录。
级联删除(删除相关记录)
在 2.26.0 及更高版本中,可以使用预览功能 referential actions 进行级联删除。
以下查询使用 delete()
删除单个 User
记录
const deleteUser = await prisma.user.delete({
where: {
email: '[email protected]',
},
})
但是,示例 schema 包含 Post
和 User
之间的必需关系,这意味着你不能删除有帖子的用户
The change you are trying to make would violate the required relation 'PostToUser' between the `Post` and `User` models.
要解决此错误,你可以
-
使关系变为可选
model Post {
id Int @id @default(autoincrement())
author User? @relation(fields: [authorId], references: [id])
authorId Int?
author User @relation(fields: [authorId], references: [id])
authorId Int
} -
在删除用户之前,将帖子的作者更改为另一个用户。
-
在事务中使用两个单独的查询删除用户及其所有帖子(所有查询都必须成功)
const deletePosts = prisma.post.deleteMany({
where: {
authorId: 7,
},
})
const deleteUser = prisma.user.delete({
where: {
id: 7,
},
})
const transaction = await prisma.$transaction([deletePosts, deleteUser])
从所有表中删除所有记录
有时你想要删除所有表中的所有数据,但保留实际的表。这在开发环境和测试时特别有用。
以下展示了如何使用 Prisma Client 和 Prisma Migrate 从所有表中删除所有记录。
使用 deleteMany()
删除所有数据
当你知晓应删除表的顺序时,可以使用 deleteMany
函数。这在 $transaction
中同步执行,并且可以与所有类型的数据库一起使用。
const deletePosts = prisma.post.deleteMany()
const deleteProfile = prisma.profile.deleteMany()
const deleteUsers = prisma.user.deleteMany()
// The transaction runs synchronously so deleteUsers must run last.
await prisma.$transaction([deleteProfile, deletePosts, deleteUsers])
✅ 优点
- 当你提前了解你的 schema 结构时,效果很好
- 同步删除每个表的数据
❌ 缺点
- 当使用关系型数据库时,此函数的扩展性不如更通用的解决方案,后者查找并
TRUNCATE
你的表,而无需考虑它们的关系约束。请注意,当使用 MongoDB 连接器时,此扩展性问题不适用。
注意:
$transaction
对每个模型的表执行级联删除,因此它们必须按顺序调用。
使用原始 SQL / TRUNCATE
删除所有数据
如果你习惯使用原始 SQL,你可以使用 $executeRawUnsafe
在表上执行 TRUNCATE
查询。
在以下示例中,第一个标签页展示了如何通过使用 $queryRaw
查找来在 Postgres 数据库上执行 TRUNCATE
,该查找映射到表并在单个查询中 TRUNCATE
所有表。
第二个标签页展示了执行相同的功能,但使用 MySQL 数据库。在这种情况下,必须先删除约束才能执行 TRUNCATE
,然后在完成后恢复约束。整个过程作为 $transaction
运行
- PostgreSQL
- MySQL
const tablenames = await prisma.$queryRaw<
Array<{ tablename: string }>
>`SELECT tablename FROM pg_tables WHERE schemaname='public'`
const tables = tablenames
.map(({ tablename }) => tablename)
.filter((name) => name !== '_prisma_migrations')
.map((name) => `"public"."${name}"`)
.join(', ')
try {
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${tables} CASCADE;`)
} catch (error) {
console.log({ error })
}
const transactions: PrismaPromise<any>[] = []
transactions.push(prisma.$executeRaw`SET FOREIGN_KEY_CHECKS = 0;`)
const tablenames = await prisma.$queryRaw<
Array<{ TABLE_NAME: string }>
>`SELECT TABLE_NAME from information_schema.TABLES WHERE TABLE_SCHEMA = 'tests';`
for (const { TABLE_NAME } of tablenames) {
if (TABLE_NAME !== '_prisma_migrations') {
try {
transactions.push(prisma.$executeRawUnsafe(`TRUNCATE ${TABLE_NAME};`))
} catch (error) {
console.log({ error })
}
}
}
transactions.push(prisma.$executeRaw`SET FOREIGN_KEY_CHECKS = 1;`)
try {
await prisma.$transaction(transactions)
} catch (error) {
console.log({ error })
}
✅ 优点
- 可扩展
- 非常快
❌ 缺点
- 无法撤消操作
- 使用保留的 SQL 关键字作为表名可能会在尝试运行原始查询时导致问题
使用 Prisma Migrate 删除所有记录
如果你使用 Prisma Migrate,你可以使用 migrate reset
,这将
- 删除数据库
- 创建一个新的数据库
- 应用迁移
- 使用数据填充数据库
高级查询示例
创建记录的深度嵌套树
- 单个 User
- 两个新的、相关的 Post 记录
- 为每篇帖子连接或创建 Category
const u = await prisma.user.create({
include: {
posts: {
include: {
categories: true,
},
},
},
data: {
email: '[email protected]',
posts: {
create: [
{
title: 'My first post',
categories: {
connectOrCreate: [
{
create: { name: 'Introductions' },
where: {
name: 'Introductions',
},
},
{
create: { name: 'Social' },
where: {
name: 'Social',
},
},
],
},
},
{
title: 'How to make cookies',
categories: {
connectOrCreate: [
{
create: { name: 'Social' },
where: {
name: 'Social',
},
},
{
create: { name: 'Cooking' },
where: {
name: 'Cooking',
},
},
],
},
},
],
},
},
})