前言
大家好啊,这里是想要创业的李卢,想要成为一名全栈工程师~
想要成为全栈,那么简单的后端肯定要学习的。最近作者在学习Nest和Prisma,发现Prisma的教程偏少,既然如此,那我就来写一篇Prisma教程,供大家一起学习进步~
本文是个人学习笔记,每一个Prisma对数据库的内置方法都给出了对应的例子,以便读者理解。
如果文章存在笔误的情况,欢迎大家在评论区交流指正
下面我们来学习,如何使用Prisma的内置方法,操作数据库
增
create({}):- Prisma的
create()方法用于在数据库中创建新的数据,接收一个配置对象,这个对象可以传入三个属性:data:必须,传入插入数据库的数据。是一个对象,对象的属性对应表的字段,属性的值对应该字段的具体值include:可选,传入对象,用来定制Response的返回结果,将需要返回的数据表对象作为属性,其值设置为true,可以返回该表关联的其他表select:可选,传入对象,用来定制Response的返回结果,将需要返回的数据表字段作为属性,其值设置为true,可以返回当前表的部分字段 例如,新增一个用户,respose只用返回用户id,其他的字段不需要返回
- Prisma的
//因为我们对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的记录
- delete方法用于从数据表中删除指定记录,传入一个配置对象,在配置对象里可传入一个属性:
prisma.user.delete({
where: {
id: 1
}
})
deleteMany({}):- deleteMany放啊用于从数据表中删除多条数据,传入一个配置对象,在配置对象里传入属性:
where:必须,过滤条件,如果省略的话会删除所有记录!一定要注意!deleteMany会删除所有满足条件的记录,例如:删除所有年龄为20的用户
- deleteMany放啊用于从数据表中删除多条数据,传入一个配置对象,在配置对象里传入属性:
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方法结合了update和insert方法,即如果有满足过滤条件的记录,则更新对应的值;如果没有满足条件的记录,则新增对应的记录。同样是传入配置对象,在配置对象中有三个属性: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=1select:可选,设置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:可选,定义返回的记录个数,默认为1skip:可选,跳过指定数量的记录select:可选,定义response中返回的字段include:可选,定义是否返回其他关联的表 例如,我们想查询姓名以李开头的,年龄最小的用户: 查询年龄最小用户,我们可以转化为,按照年龄增序排序,返回第一个记录
prisma.user.findFirst({
where: {
name: {
startsWith: "李"
}
},
orderBy: {
age: "asc"
}
})
findMany({}):用于查询多条记录,传入配置对象,配置对象可有六个属性where:可选,配置过滤条件orderBy:可选,指定如果有多个符合条件的记录时的排列方式,返回排序结果中的第一个记录skip:可选,跳过指定数量的记录take:可选,定义返回的记录个数,默认为1select:可选,定义response中返回的字段include:可选,定义是否返回其他关联的表 findMany()可能是我们接触最多的方法了,既然用的多,我们就多举几个例子
查询所有记录
不设置任何过滤条件,即查询所有记录
prisma.user.findMany()
查询所有满足条件的记录
通过where设置过滤条件,查询所有年龄小于20的用户
prisma.user.findMany({
where: {
age: {
lt: 20
}
}
})
分页查询
通过findMany()方法和take与skip配置,可以实现数据库分页查询功能
take:可选,定义返回的记录个数skip:可选,跳过指定数量的记录 假设每一页返回20条数据,那么take的值为20,skip初始为0,每翻到下一页,skip的值加20。例如: 第一页:take: 20skip: 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语句,插入新用户:
- 字符串传参:将sql语句作为字符串,传入
//字符串传参写法
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}`