一篇文章通关Prisma所有的增删改查方法,Prisma详细教程

3,216 阅读14分钟

前言

大家好啊,这里是想要创业的李卢,想要成为一名全栈工程师~

想要成为全栈,那么简单的后端肯定要学习的。最近作者在学习Nest和Prisma,发现Prisma的教程偏少,既然如此,那我就来写一篇Prisma教程,供大家一起学习进步~

本文是个人学习笔记,每一个Prisma对数据库的内置方法都给出了对应的例子,以便读者理解。

如果文章存在笔误的情况,欢迎大家在评论区交流指正

下面我们来学习,如何使用Prisma的内置方法,操作数据库

  • create({})
    • Prisma的create()方法用于在数据库中创建新的数据,接收一个配置对象,这个对象可以传入三个属性:
      • data:必须,传入插入数据库的数据。是一个对象,对象的属性对应表的字段,属性的值对应该字段的具体值
      • include:可选,传入对象,用来定制Response的返回结果,将需要返回的数据表对象作为属性,其值设置为true,可以返回该表关联的其他表
      • select:可选,传入对象,用来定制Response的返回结果,将需要返回的数据表字段作为属性,其值设置为true,可以返回当前表的部分字段 例如,新增一个用户,respose只用返回用户id,其他的字段不需要返回
//因为我们对user表进行操作,因此通过代码prisma.user进行操作
prisma.user.create({
	data: {
		id: 1,
		name: "李卢",
		age: 20
	},
	//要注意,incluide和select只需要选择一个就好,这里我们返回本表的字段,因此使用select
	select: {
		id: true
	}
})
  • createMany({})
    • 可以批量创建新的数据,一次性在数据库中添加多条记录,传入一个配置对象,这个配置对象可以传入两个属性:
      • data:必须,传入一个数组,数组中存储对象,最终将数组中每一个对象添加到数据库中
      • skipDuplicates:可选,传入布尔值,如果为true的话,遇到唯一约束重复的记录,会跳过,而不是引发错误 例如,我们批量添加用户,现在user表里已经有了id为1的记录:
prisma.user.createMany({
	data: [
		{
			id: 1,
			name: "李大卢",
			age: 21
		},
		{
			id: 2,
			name: "李小卢",
			age: 18
		}
	],
	//将skipDuplicates的值设置为true,这样当插入id为1,name为李大卢的记录时,会自动跳过,不会报错
	skipDuplicates: true
})

删(包含条件过滤)

  • delete({})
    • delete方法用于从数据表中删除指定记录,传入一个配置对象,在配置对象里可传入一个属性:
      • where:必须,用键值对表示约束,例如删除id为1的记录
prisma.user.delete({
	where: {
		id: 1
	}
})
  • deleteMany({})
    • deleteMany放啊用于从数据表中删除多条数据,传入一个配置对象,在配置对象里传入属性:
      • where:必须,过滤条件,如果省略的话会删除所有记录!一定要注意!deleteMany会删除所有满足条件的记录,例如:删除所有年龄为20的用户
prisma.user.deleteMany({
	where: {
		age: 20
	}
})

看到这里,你是否好奇,我如果想要删除所有年龄小于20的用户呢?这样如何用键值对来表示呢?如果我要删除满足两个条件之一的记录呢?这又该如何编写呢?

那么现在,我们这就来深入学习,where子句里面的各种操作符

where条件过滤的四种操作符

在Prisma中,有四种操作符可以对字段进一步过滤,分别是:

  • 比较操作符
  • 字符串操作符
  • 逻辑操作符
  • 空值检查
比较操作符
  • equals:字段等于某一个值 例如:我想要删除age字段等于20的所有记录
prisma.user.delete({
	where: {
		age: {
			equals: 20
		}
	}
})
  • not:字段不等于某个值 例如:我想要删除name字段不等于李卢的所有记录:
prisma.user.delete({
	where: {
		name: {
			not: "李卢"
		}
	}
})
  • in:字段在指定数组中 例如:我想要删除名字为李大卢李小卢的所有记录
prisma.user.delete({
	where: {
		name: {
			in: ["李大卢", "李小卢"]
		}
	}
})
  • notIn:字段不在指定数组中 例如:我想要删除名字不为李大卢李小卢的所有记录
prisma.user.delete({
	where: {
		name: {
			notIn: ["李大卢", "李小卢"]
		}
	}
})
  • lt:字段小于某个值 例如:我想要删除所有年龄小于20的所有记录
prisma.user.delete({
	where: {
		age: {
			lt: 20
		}
	}
})
  • lte:字段小于或等于某个值
  • gt:字段大于某个值
  • gte:字段大于或等于某个值
字符串操作符
  • contains:字段包含指定字符串 例如,我想要删除所有名字中间包含的记录
prisma.user.delete({
	where: {
		name: {
			contains: "大"
		}
	}
})
  • startsWith:字段以指定字符串开头 例如,我想要删除所有名字以开头的记录:
prisma.user.delete({
	where: {
		name: {
			startWith: "李"
		}
	}
})
  • endsWith:字段以指定字符串结束 例如,我想要删除所有名字以结尾的记录:
prisma.user.delete({
	where: {
		name: {
			endsWith: "卢"
		}
	}
})
逻辑操作符
  • AND:同时满足多个条件,赋值一个数组,要同时满足数组里每个对象约束 假设我要删除所有,20岁且名字以开头的记录:
prisma.user.delete({
	where: {
		AND: [
			{
				name: {
					startsWith: "李"
				}
			},
			{
				age: {
					equals: 20
				}
			}
		]
	}
})
  • OR:满足任一条件,赋值一个数组,要满足数组中任意一个对象约束 假设我要删除所有,年龄为20或者以结尾的记录:
prisma.user.delete({
	where: {
		OR: [
			{
				age: {
					equals: 20   
				}
			},
			{
				name: {
					endsWith: "卢"
				} 
			}
		]
	}
})

当然,AND和OR可以嵌套,我们来一个比较难的例子: 我要删除 年龄小于20且以开头 或 年龄大于20且以结尾的所有记录:

prisma.user.delete({
	where: {
		OR: [
			{
				AND: [
					{
						age: {
							lt: 20
						},
						name: {
							startsWith: "李"
						}
					}
				]
			},
			{
				AND: [
					{
						age: {
							lt: 20
						},
						name: {
							startsWith: "卢"
						}
					}
				]
			}
		]
	}
})
  • NOT:不满足指定条件,同样赋值数组,不满足数组中所有的对象约束 假设我要删除所有年龄不为20,且名字不以开头的所有记录:
prisma.user.delete({
	where: {
		NOT: [
			{
				//equals也可以直接写成这样子
				age: 20
			},
			{
				name: {
					startsWith: "李"
				}
			}
		]
	}
})
空值检查
  • is:检查字段是否为null 假设有些用户没有填入年龄,我们想要找到所有年龄为null的用户:
prisma.user.findMany({
	where: {
		age: {
			is: null
		}
	}
})
  • isNot:检查字段是否不为null 假设我们要找到所有年龄不为null的用户:
prisma.user.findMany({
	where: {
		age: {
			isNot: null
		}
	}
})

现在我们知道了如何如何使用where进行条件约束,接下来我们继续学习增删改查中的改和查

  • update({}):传入配置对象,对象中可以有两个属性:
    • where:必须,我们刚刚学完
    • data:必须,要修改的字段和值
    • include:可选,指定返回的Response中应当包含哪些其他表中的字段
    • select:可选,指定返回的Response中应当包含本表的哪些字段 例如,我们已经有了id为1的用户记录,现在我们要将其name字段改为"李中卢",并且response返回id字段和name字段:
prisma.user.update({
	where: {
		id: 1
	},
	data: {
		name: "李中卢"
	},
	select: {
		id: true,
		name: true
	}
})
  • updateMany({}):传入配置对象,在配置对象里有两个属性:
    • where:必须,设置过滤条件,满足条件的记录会被更新
    • data:必须,要修改的字段和值 例如,我们将所有小于18岁的用户的adult字段设置为false:
prisma.user.updateMany({
	where: {
		age: {
			lt: 20
		}
	},data: {
		adult: false
	}
})
  • upsert({}):upsert方法结合了updateinsert方法,即如果有满足过滤条件的记录,则更新对应的值;如果没有满足条件的记录,则新增对应的记录。同样是传入配置对象,在配置对象中有三个属性:
    • where:必须,设置过滤条件,如果没有满足对应条件的记录,则新增create里的记录;反之更新update里的记录
    • create:必须,设置新增的记录
    • update:必须,设置更新的记录 upsert()很适合处理数据库中可能有,可能没有的值,通过upsert()我们就不需要提前查询记录是否存在。 例如,我们想要对名字为李卢的记录进行更新,将年龄设置为21。我们不知道数据库中有没有名字为李卢的记录,如果有的话我们就更新;如果没有我们就新增:
prisma.user.upsert({
	where: {
		name: "李卢"
	},
	create: {
		name: "李卢",
		age: 20
	},
	update: {
		age: 20
	}
})

查(包含连表查询)

我们接下来在案例里介绍,如何在prisma实现连表查询,即通过join将两个表做连接:

  • findUnique({}),用于查找具有唯一标识的记录,查询的结果应当是唯一的。传入配置对象,在配置对象上可以有三个属性:
    • where:必须,设置过滤条件,条件应当找出唯一标识的记录,如id=1
    • select:可选,设置response应当返回本表的字段
    • include:可选,指定返回的response中应当还包含哪些数据表中的所有字段,适合联表查询,如从用户表查找id为1的用户,同时在评论表里查询该用户留下的所有评论

请注意,select和include同时只能选择一个使用,因为这两个属性都会配置response返回的数据,因此这两个配置互斥

prisma.user.findUnique({
	where: {
		id: 1
	},
	include: {
		comment: true
	}
})

详解include,prisma里如何实现联表查询

首先我们要知道,在prisma里面没有类似于join的操作符。如果我想要查询id为1的用户,在用户表里的个人信息和评论表中该用户的所有评论,我们需要先在model里面定义用户表和评论表的联系,然后在查询的时候通过include属性进行类似于join的操作

接下来跟着作者的思路,一步步实现联表查询

建立表和表之间的联系

我们首先给出user表和comment表的model定义

model user {
  id    Int     @id @default(autoincrement())
  name  String
  age   Int
  comment Comment[]  // 用户和评论之间的一对多关系,说明一个用户可以有多个评论
}

model comment {
  id       Int    @id @default(autoincrement())
  content  String
  userId   Int    // 外键,指向用户表的id,标识当前评论是谁发的
  user     User   @relation(fields: [userId], references: [id]) // 设置关系和外键,fields指定外键,即comment表的userId字段;references指定当前表的唯一标识,即本表的id
}

首先在user表中,通过comment Comment[]定义了用户和评论之间的一对多关系,即一个用户可以有多个评论。 然后在coment表中,先通过user User定义了评论与用户之间一对一的关系,然后用@relation进一步描述这种一对一关系:

已知评论和用户之间的关系是一对一的,且用户决定评论,我们令用户表为关联表,评论表为被关联表,则通过@relations给出以下定义:

  • fields指定外键,即user表的id,用来指定关联表中用来建立关系的字段;
  • references指定本表的唯一标识,一般是本表的id,即comment表的id,用来指定被关联表中的唯一标识

也就是说,通过usr User定义了评论到用户的一对一关系,通过 @relation(fields: [userId], references: [id])详细的描述了:评论到用户的一对一关系,是通过用户表的id与评论表中的id这两个字段建立的。

通过include指定返回的联表

我们在model里面定义好所有的关系后,联表查询工作就变得很简单了。我们只需要在include中,指定查询结果中,需要包含哪些联表即可,这时候我们再看下面的查询

prisma.user.findUnique({
	where: {
		id: 1
	},
	include: {
		comment: true
	}
})

也就看的懂了,user表里面有comment Comment[]字段,用来表示每一个用户有多个评论。在include里指定comment为true,即按照comment表里定义的联系,返回用户id为1的评论表

最后的输出结果类似于:

{
  "id": 1,
  "name": "李卢",
  "age": 20,
  "comments": [
    {
      "id": 101,
      "content": "大家好呀!",
      "userId": 1
    },
    {
      "id": 102,
      "content": "如果文章里存在纰漏,欢迎指正~",
      "userId": 1
    }
    // 其他评论
  ]
}

下面我们继续学习剩下的查询操作

  • findFirst({}):看名字就知道,这是返回第一个满足过滤条件的记录,传入配置对象,配置对象有7个属性:
    • where:可选,配置过滤条件
    • orderBy:可选,指定如果有多个符合条件的记录时的排列方式,返回排序结果中的第一个记录
    • cursor:可选,用于分页和定位
    • take:可选,定义返回的记录个数,默认为1
    • skip:可选,跳过指定数量的记录
    • select:可选,定义response中返回的字段
    • include:可选,定义是否返回其他关联的表 例如,我们想查询姓名以开头的,年龄最小的用户: 查询年龄最小用户,我们可以转化为,按照年龄增序排序,返回第一个记录
prisma.user.findFirst({
	where: {
		name: {
			startsWith: "李"
		}
	},
	orderBy: {
		age: "asc"
	}
})
  • findMany({}):用于查询多条记录,传入配置对象,配置对象可有六个属性
    • where:可选,配置过滤条件
    • orderBy:可选,指定如果有多个符合条件的记录时的排列方式,返回排序结果中的第一个记录
    • skip:可选,跳过指定数量的记录
    • take:可选,定义返回的记录个数,默认为1
    • select:可选,定义response中返回的字段
    • include:可选,定义是否返回其他关联的表 findMany()可能是我们接触最多的方法了,既然用的多,我们就多举几个例子

查询所有记录

不设置任何过滤条件,即查询所有记录

prisma.user.findMany()

查询所有满足条件的记录

通过where设置过滤条件,查询所有年龄小于20的用户

prisma.user.findMany({
	where: {
		age: {
			lt: 20
		}
	}
})

分页查询

通过findMany()方法和takeskip配置,可以实现数据库分页查询功能

  • take:可选,定义返回的记录个数
  • skip:可选,跳过指定数量的记录 假设每一页返回20条数据,那么take的值为20,skip初始为0,每翻到下一页,skip的值加20。例如: 第一页:
  • take: 20
  • skip: 0 第二页:
  • take: 20
  • skip: 20 以此类推,下面是nest里面的实现
let take: 20
let page = 1
prisma.user.findMany({
	take: take,
	skip: take*page,
})

下面我们继续学期查询操作的其他方法

count 计数

  • count({}):count方法用于计数,返回满足过滤条件的记录个数。传入配置对象,配置对象可传入一个属性:
    • where:可选,编写过滤条件。如果不编写过滤条件的话,返回所有记录的个数 例如,我们想要统计年龄在20岁以下的人数:
prisma.user.count({
	where: {
		age: {
			lt: 20
		}
	}
})

aggregate 执行聚合函数

  • aggregate({}):aggregate方法用于执行各种聚合函数,如计数、求和、求平均数、求最大值和最小值等。传入配置对象,配置对象可有6个属性:
    • where:可选,编写过滤条件。如果有的话,则对满足过滤条件的记录做聚合操作;没有的话对所有记录做集合操作
    • _count:可选,值为布尔值,为true则对查询结果计数
    • _avg:可选,值为对象,在对象里指定需要求平均值的字段,将其值设置为true
    • _sum:可选,值为对象, 在对象里指定需要求和的字段,将其值设置为true
    • _min:可选,值为对象, 在对象里指定需要求最小值的字段,将其值设置为true
    • _max:可选,值为对象, 在对象里指定需要求最大值的字段,将其值设置为true 假设我们需要求出用户年龄的最大值、最小值、平均值:
let result = await prisma.user.aggregate({
	where: {
		age: {
			isNot: null
		}
	},
	_max: {
		age: true
	},
	_min: {
		age: true
	},
	_avg: {
		age: true
	}
})

console.log(result)

最终输出一个对象,在对象里有_max等属性,属性里的值为对应的值

{
  "_max": {
    "age": 20
  },
  "_min": {
    "age": 19
  },
  "_avg": {
    "age": 21
  }
}

原生SQL

在某些情况下,通过prisma自带的方法很难完成复杂的,这时候使用原生SQL语句或许才是更好的选择,例如:如果你实在无法理解model的表关系,那还是可以用join进行连表查询操作的。

prisma提供两种方法执行原生SQL语句:

  • $executeRaw:允许运行增删改的SQL语句,如update、insert等。有两种方法编写sql语句:
    • 字符串传参:将sql语句作为字符串,传入$executeRaw()方法中
    • 模板字符串:使用模板字符串编写sql语句,此时需要去掉$executeRaw的括号 下面我们用mySql语句,插入新用户:
//字符串传参写法
prisma.user.$executeRaw("INSERT user(name, age) VALUES('李卢', 20)")
//模板字符串写法
let name = "李卢"
let age = 20
prisma.user.$executeRaw`INSERT user(name, age) VALUES(${name}, ${age})`
  • $queryRaw:运行运行查询的SQL语句,如select 下面我们用mySql语句,查询名字为李卢的用户
//字符串传参写法
let result = await prisma.user.$queryRaw("SELECT * FROM user WHERE name=李卢")
//模板字符串写法
let name = "李卢"
let result = await prisma.user.$queryRaw`SELECT * FROM user WHERE name=${name}`