mongodb操作实例

介绍

这是mongodb的第二篇文章,本文包含了一些在平时自己开发过程当中使用的一些操作符以及特殊情况,希望对各位有帮助😺 。

正文

$filter

  • 用于从已有数据对象中的数组中筛选出符合条件的数据项

  • 假设有如下集合名称为users的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
students: [
{
name: "Join",
age: 10
},
{
name: "Lisa",
age: 19
},
{
name: "Jack",
age: 17
}
]
}
]
  • 筛选出年龄大于18的学生
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
db.users.aggregate([
{
$project: {
student_gt_18: {
$filter: {
input: "$students",
as: "student",
cond: {
$gt: [ "$$student.age", 18 ]
}
}
}
}
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
[
{
student_gt_18: [
{
name: "Lisa",
age: 19
}
]
}
]

$filter有三个参数:

  1. input
    元数据中的某一字段(如students)
  2. as
    students的遍历项名称(如student)
  3. cond
    筛选条件,可以在其中使用遍历项的值(如$$student.age,得到了当前项的age)

$map

  • 用于遍历已有数据对象中的数组中并解析成新的数组
    其实这个和上面的$filter在语义上与javascript的同名方法的功能是一致的,这样的话应该会很好理解了👍 。

  • 假设有如下集合名称为users的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
students: [
{
name: "Join",
age: 10
},
{
name: "Lisa",
age: 19
}
]
}
]
  • 将学生nameage字段拼接生成新的字段description
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
db.users.aggregate([
{
$project: {
new_students: {
$map: {
input: "$students",
as: "student",
in: {
description: {
$concat: [ "$$student.name", "-", "$$student.age" ]
}
}
}
}
}
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
[
{
new_students: [
{
description: "Join-10",
},
{
description: "Lisa-19",
}
]
}
]

$map有三个参数:

  1. input
    元数据中的某一字段(如students)
  2. as
    students的遍历项名称(如student)
  3. in
    将会生成的数据字段,key为字段名称,value为字段值,可以在其中使用遍历项的值(如$$student.age,得到了当前项的age)
    甚至可以在其中对一些数组进行$filter操作,这是被允许的。

$lookup

  • 复杂多表联查
    这个应该在平常开发中用到的频率非常的高,用于将嵌套的集合进行查询
    想象一下可能你的users集合中保存着teacher的字段,值为另一个teachers集合的id,此时就需要用到$lookup来进行查询

$lookup有两种查询形式

简单查询

这种适合只查询一层的情况,比如上面说的teachers集合中不存在当前查询所需要再次联表查询的字段
听着有些别扭,看下面的例子

  • 假设有如下集合名称为usersteachers的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_001"
}
]

// teachers
[
{
id: "custom_teacher_id_001",
name: "Lisa",
age: 40
}
]
  • 查询students集合并同时查询出其中的teacher数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
db.users.aggregate([
{
$lookup: {
from: 'teachers',
localField: 'teacher',
foreignField: 'id',
as: 'teacher_data'
}
},
{
$project: {
id: 1,
name: 1,
age: 1,
teacher_data: 1
}
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
// 注意这里
teacher_data: [
{
id: "custom_teacher_id_001",
name: "Lisa",
age: 40
}
]
}
]

注意看上面的查询出来的数据的teacher_data字段,它是一个数组。通过$lookup查询的结果都会变成一个数组。
如果不想是数组,可以通过$unwind来进行拆分,有关$unwind可在后文看到。

简单查询包含四个参数:

  1. from
    要查询的目标集合名称
  2. localField
    当前集合中需要进行查询的字段名称
  3. foreignField
    两个集合所关联起来的字段名称,上面是id
  4. as
    查询结果保存的字段名称,你可以使用原始字段名称进行覆盖,也可以新增一个

复杂查询

通过上面的例子应该可以理解刚刚的那句话了吧,复杂查询可以把那些嵌套关联了多层的数据查询出来
还是看下面的例子🌰

  • 假设有如下集合名称为usersteachers以及schools的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_001"
}
]

// teachers
[
{
id: "custom_teacher_id_001",
name: "Lisa",
age: 40,
school: "custom_school_id_001"
}
]

// schools
[
{
id: "custom_school_id_001",
name: "high school",
}
]
  • 查询students集合并同时查询出其中的teacher数据以及school数据
    此时通过简单得查询已经无法满足要求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
db.users.aggregate([
{
$lookup: {
from: 'teachers',
let: {
teacher_id: "$teacher"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$id", "$$teacher_id"
]
}
}
},
{
$lookup: {
from: 'schools',
as: 'school_data',
foreignField: "id",
localField: "school"
}
},
{
$project: {
id: 1,
name: 1,
school: 1,
age: 1,
school_data: "$school_data"
}
}
],
as: 'teacher_data'
}
},
{
$project: {
id: 1,
name: 1,
age: 1,
teacher_data: 1
}
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher_data: [
{
id: "custom_teacher_id_001",
name: "Lisa",
age: 40,
school_data: [
{
id: "custom_school_id_001",
name: "high school",
}
]
}
]
}
]

复杂查询也是四个参数:

  1. from
    与简单查询相同
  2. as
    与简单查询相同
  3. let
    在当前查询层定义的变量可以在本次查询中使用,比如上面定义的teacher_id,用于在下层查询时做筛选条件判断
  4. pipeline
    下层查询操作,顾名思义,管道操作,值是一个数组,当中可以使用与外层相同的查询操作,可以访问到上层let中定义的变量,使用$$前缀
  • 应该有注意到上面在pipeline第一项是$match,为什么要这样操作?
    当使用复杂查询时,需要自己来定义筛选条件,否则它将会把集合当中的所有数据全部返回

  • $match 表示的是筛选条件
    以及其中若使用到let定义的变量时,需要使用$expr操作符,具体的我还没有了解过😊 。
    还有一点需要注意的是,当要做比较的值得类型是mongodb自带的ObjectId类型时,相等判断条件需要使用$eq操作符,否则永远返回false

  • 注意:

  • pipeline 中使用 $match 匹配 let中定义的字段时,需要在外面包一个 $expr, 否则无法匹配
    $match: { $expr: { _id: "$$customFields" } }

  • 如果要在$match中匹配ObjectId, 需要使用$eq, 直接比较似乎无效,原因有待查证. $expr: { $eq: [ "$_id", "$$customFields" ] }

  • ps
    这是我另外碰到的一个例子🌰
    需要判断某个值是否在数组中存在,此时可以使用$in操作符进行判断
    第一个参数是需要判断的值,第二个参数是查询的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 简单描述一下
{
let: {
teacher_id: "$teacher"
},
pipeline: [
{
$match: {
$expr: {
"$in": [ "$name", ["Lisa"] ]
}
}
}
]
}

$unwind

根据指定的数组字段进行拆分成多项
它也有两种形式

简单形式

  • 假设有如下集合名称为users的数据:
1
2
3
4
5
6
7
8
9
10
11
12
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: [
"custom_teacher_id_001",
"custom_teacher_id_002"
]
}
]
  • 查询users数据并将teacher字段进行拆分
1
2
3
4
5
db.users.aggregate([
{
$unwind: "$teacher"
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_001"
},
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_002"
}
]

teacher为拆分的字段,需要添加$前缀
这种简单的写法适合那种字段规整的情况,当需要做异常处理时,这种情况就不适用了。

复杂形式

  • 假设有如下集合名称为users的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: [
"custom_teacher_id_001",
"custom_teacher_id_002"
]
},
{
id: "custom_student_id_002",
name: "Lisa",
age: 20,
}
]
  • 查询users数据并将teacher字段进行拆分,并设置当teacher不存在时继续保留
1
2
3
4
5
6
7
8
db.users.aggregate([
{
$unwind: {
path: "$teacher",
preserveNullAndEmptyArrays: true
}
}
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_001"
},
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: "custom_teacher_id_002"
},
{
_id: "custom_student_id_002",
name: "Lisa",
age: 20,
}
]

如上可以看到,当teacher不存在时,保留了对应的字段,如果设置preserveNullAndEmptyArrays时,第三条数据将不被查询到。

复杂形式有三个参数:

  1. path
    同简单形式
  2. preserveNullAndEmptyArrays
    是否保留空数组
  3. includeArrayIndex
    暂时没有用到过,再说。

$addToSet

这个方法在前面的mongodb常用操作符文章中介绍过介绍过,但是那是添加一项,有时候我们需要同时添加多个项。

  • 假设有如下集合名称为users的数据:
1
2
3
4
5
6
7
8
9
10
11
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: [
"custom_teacher_id_001",
]
},
]
  • nameJoin的数据字段添加两个teacher
1
2
3
4
5
6
7
8
9
10
11
12
db.users.updateOne({
name: "Join"
}, {
$addToSet: {
teacher: {
$each: [
"custom_teacher_id_002",
"custom_teacher_id_003"
]
}
}
})
  • 该字段会变成
1
2
3
4
5
6
7
8
9
10
11
12
[
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
teacher: [
"custom_teacher_id_001",
"custom_teacher_id_002",
"custom_teacher_id_003"
]
},
]

配合$each添加多项,这个没啥好说的

$addFields

  • 向输出结果中新增字段
    这种的一般使用情况是对原始的一些数据字段做特殊处理,比如统计等。

  • 假设有如下集合名称为users的数据:

1
2
3
4
5
6
7
8
9
10
11
12
// users 
[
{
id: "custom_student_id_001",
name: "Join",
age: 18,
homework: [
30,
40
]
}
]
  • 查询users数据并添加新字段total_homework
1
2
3
4
5
6
7
8
9
db.users.aggregate([
{
$addFields: {
total_homework: {
$sum: "$homework"
}
}
},
])
  • 你将会得到
1
2
3
4
5
6
7
8
9
10
11
12
[
{
_id: "custom_student_id_001",
name: "Join",
age: 18,
homework: [
30,
40
],
total_homework: 70
},
]

$push

这个也是在前一篇文章有介绍

但是当需要同时添加多项时,可能会需要用到$pushAll操作符,但是在实际使用过程中,$pushAll会报错,所以这里还是使用$push
至于使用方法,可以参考前面的$addToSet操作符。

结束

以上就是本人在实际使用过程中碰到的一部分问题,以后如果有新的问题还会补充。如果有哪里错了欢迎指正😊 。