CRUD
此页面描述了如何使用生成的 Prisma 客户端 API 执行 CRUD 操作。CRUD 是一个首字母缩写词,代表
有关每种方法的详细说明,请参阅 Prisma 客户端 API 参考文档。
示例架构
所有示例都基于以下架构
展开以查看示例架构
- 关系型数据库
- 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
命令将示例架构推送到您自己的数据库
npx prisma db push
对于 **MongoDB**,请确保您的数据格式一致,并且与 Prisma 架构中定义的模型匹配。
创建
创建单个记录
以下查询使用 (create()
) 创建一个包含两个字段的单个用户
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Elsa Prisma',
},
})
用户的 id
是自动生成的,并且您的架构决定 哪些字段是必需的。
使用生成的类型创建单个记录
以下示例会产生相同的结果,但会在 create()
查询的上下文之外创建一个名为 user
的 UserCreateInput
变量。在完成简单检查(“此 create()
查询中是否应包含帖子?”)后,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 客户端在 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 或唯一标识符获取记录
以下查询使用 (findUnique()
) 根据唯一标识符或 ID 返回单个记录
// 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 是最近的
- 按降序返回第一个至少包含一篇点赞数超过 100 的帖子的用户
const findUser = await prisma.user.findFirst({
where: {
posts: {
some: {
likes: {
gt: 100,
},
},
},
},
orderBy: {
id: 'desc',
},
})
获取过滤后的记录列表
Prisma 客户端支持对记录字段和相关记录字段进行 过滤。
根据单个字段值进行过滤
以下查询返回所有以 "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: '[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,
},
},
},
})
有关包含关系的更多信息,请参见选择字段和包含关系。
选择不同的字段值
有关选择不同字段值的详细信息,请参见选择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: '[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',
},
})
更新或创建记录
以下查询使用upsert()
更新具有特定电子邮件地址的User
记录,或者如果该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({})
请注意,如果用户有任何相关记录(如帖子),此查询将失败。在这种情况下,您需要先删除相关记录。
级联删除(删除相关记录)
以下查询使用delete()
删除单个User
记录
const deleteUser = await prisma.user.delete({
where: {
email: '[email protected]',
},
})
但是,示例模式在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: '[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',
},
},
],
},
},
],
},
},
})