跳至主要内容

CRUD

此页面描述了如何使用生成的 Prisma 客户端 API 执行 CRUD 操作。CRUD 是一个首字母缩写词,代表

有关每种方法的详细说明,请参阅 Prisma 客户端 API 参考文档

示例架构

所有示例都基于以下架构

展开以查看示例架构
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
}

对于 **关系型数据库**,使用 db push 命令将示例架构推送到您自己的数据库

npx prisma db push

对于 **MongoDB**,请确保您的数据格式一致,并且与 Prisma 架构中定义的模型匹配。

创建

创建单个记录

以下查询使用 (create()) 创建一个包含两个字段的单个用户

const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Elsa Prisma',
},
})
显示查询结果

用户的 id 是自动生成的,并且您的架构决定 哪些字段是必需的

使用生成的类型创建单个记录

以下示例会产生相同的结果,但会在 create() 查询的上下文之外创建一个名为 userUserCreateInput 变量。在完成简单检查(“此 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 的帖子

  1. 按降序 ID 排序用户(从最大值开始) - 最大的 ID 是最近的
  2. 按降序返回第一个至少包含一篇点赞数超过 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 记录的 emailname 字段

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,
},
})
显示查询结果

有关包含关系的更多信息,请参见选择字段和包含关系

包含过滤后的关系列表

请参见使用关系,了解如何组合includewhere以获得过滤后的关系列表,例如,仅包含用户的已发布帖子。

更新

更新单个记录

以下查询使用update()通过email查找和更新单个User记录

const updateUser = await prisma.user.update({
where: {
email: '[email protected]',
},
data: {
name: 'Viola the Magnificent',
},
})
显示查询结果

更新多个记录

以下查询使用updateMany()更新所有包含prisma.ioUser记录

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()

更新数字字段

使用原子数字操作来更新数字字段基于其当前值 - 例如,递增或乘以。以下查询将viewslikes字段递增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.ioUser记录

const deleteUsers = await prisma.user.deleteMany({
where: {
email: {
contains: 'prisma.io',
},
},
})

尝试删除具有一个或多个帖子的用户会导致错误,因为每个Post都需要一个作者 - 请参见级联删除

删除所有记录

以下查询使用deleteMany()删除所有User记录

const deleteUsers = await prisma.user.deleteMany({})

请注意,如果用户有任何相关记录(如帖子),此查询将失败。在这种情况下,您需要先删除相关记录

警告

2.26.0及更高版本中,可以使用预览功能引用操作来执行级联删除。

以下查询使用delete()删除单个User记录

const deleteUser = await prisma.user.delete({
where: {
email: '[email protected]',
},
})

但是,示例模式在PostUser之间包含一个必需关系,这意味着您无法删除具有帖子的用户

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运行

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 })
}

优点

  • 可扩展
  • 非常快

缺点

  • 无法撤消操作
  • 使用保留的 SQL 关键字作为表名可能会在尝试运行原始查询时导致问题

使用 Prisma Migrate 删除所有记录

如果您使用 Prisma Migrate,则可以使用migrate reset,这将

  1. 删除数据库
  2. 创建一个新的数据库
  3. 应用迁移
  4. 使用数据填充数据库

高级查询示例

创建深度嵌套的记录树

  • 单个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',
},
},
],
},
},
],
},
},
})