CRUD
本页面介绍了如何使用生成的 Prisma Client API 执行 CRUD 操作。CRUD 是以下操作的首字母缩写:
有关每种方法的详细说明,请参阅Prisma Client API 参考文档。
示例模式
所有示例均基于以下模式
展开查看示例模式
- 关系型数据库
- MongoDB
datasource db {
provider = "postgresql"
}
generator client {
provider = "prisma-client"
output = "./generated"
}
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 命令将示例模式推送到您自己的数据库
npx prisma db push
对于 MongoDB,请确保您的数据形状统一并与 Prisma 模式中定义的模型匹配。
创建
创建单个记录
以下查询创建(create())一个具有两个字段的单个用户
const user = await prisma.user.create({
data: {
email: 'elsa@prisma.io',
name: 'Elsa Prisma',
},
})
用户的 id 是自动生成的,您的模式决定了哪些字段是强制性的。
使用生成的类型创建单个记录
以下示例产生相同的结果,但在 create() 查询上下文之外创建了一个名为 user 的 UserCreateInput 变量。在完成一个简单检查(“此 create() 查询中是否应包含帖子?”)后,user 变量被传递到查询中。
import { PrismaClient, Prisma } from '../prisma/generated/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: 'elsa@prisma.io',
name: 'Elsa Prisma',
posts: {
create: {
title: 'Include this post!',
},
},
}
} else {
user = {
email: 'elsa@prisma.io',
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: 'bob@prisma.io' },
{ name: 'Bobo', email: 'bob@prisma.io' }, // Duplicate unique key!
{ name: 'Yewande', email: 'yewande@prisma.io' },
{ name: 'Angelique', email: 'angelique@prisma.io' },
],
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: 'alice@prisma.io' },
{ name: 'Bob', email: 'bob@prisma.io' },
],
})
使用 createManyAndReturn() 时,relationLoadStrategy: join 不可用。
读取
按 ID 或唯一标识符获取记录
以下查询按唯一标识符或 ID 返回单个记录(findUnique())
// By unique identifier
const user = await prisma.user.findUnique({
where: {
email: 'elsa@prisma.io',
},
})
// 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 支持对记录字段和相关记录字段进行过滤。
按单个字段值过滤
以下查询返回所有邮箱以 "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',
},
},
},
],
},
})
按相关记录字段值过滤
以下查询返回邮箱以 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: 'emma@prisma.io',
},
select: {
email: true,
name: true,
},
})
有关包含关系的更多信息,请参阅
选择相关记录字段的子集
以下查询使用嵌套的 select 返回
- 用户的
email - 每篇帖子的
likes字段
const user = await prisma.user.findUnique({
where: {
email: 'emma@prisma.io',
},
select: {
email: true,
posts: {
select: {
likes: true,
},
},
},
})
有关包含关系的更多信息,请参阅选择字段和包含关系。
选择不同的字段值
有关选择不同字段值的信息,请参阅选择 distinct。
包含相关记录
以下查询返回所有 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: 'viola@prisma.io',
},
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() 更新具有特定电子邮件地址的 User 记录,如果该 User 记录不存在则创建它。
const upsertUser = await prisma.user.upsert({
where: {
email: 'viola@prisma.io',
},
update: {
name: 'Viola the Magnificent',
},
create: {
email: 'viola@prisma.io',
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: 'bert@prisma.io',
},
})
尝试删除具有一个或多个帖子的用户会导致错误,因为每个 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({})
请注意,如果用户有任何相关记录(例如帖子),此查询将失败。在这种情况下,您需要首先删除相关记录。
级联删除(删除相关记录)
以下查询使用 delete() 删除单个 User 记录
const deleteUser = await prisma.user.delete({
where: {
email: 'bert@prisma.io',
},
})
但是,示例模式包含 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])
✅ 优点
- 当您提前知道模式结构时,效果很好
- 同步删除每个表的数据
❌ 缺点
- 在使用关系型数据库时,此函数的扩展性不如更通用的解决方案(无论关系约束如何,查找并
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: 'emma@prisma.io',
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',
},
},
],
},
},
],
},
},
})