avatar


1.基础

开始

什么是MongoDB

MongoDB,一种非关系型数据库,使用JSON样式的文档模型存储数据,适合非结构化数据的场景。

官网:https://www.mongodb.com/zh-cn
官方文档:https://www.mongodb.com/zh-cn/docs/manual/

特点:

  • 文档模型
    基于集合的文档存储方式。
  • 灵活的模式
    无需预定义模式,支持动态添加字段。
  • 丰富的查询方式。
  • 多语言驱动支持
    官方支持多种编程语言的驱动程序,包括Java、Python、C#、Ruby等。
  • BSON数据格式
    内部使用BSON(Binary JSON)格式存储数据,相较于纯文本JSON更加紧凑和高效。
  • 分布式架构:副本、分片
  • ACID事务
    从4.0开始引入多文档ACID事务。
  • 内置聚合框架
    用于执行复杂的数据分析任务,例如过滤、排序、分组等。
  • 索引
    支持创建各种类型的索引来加速查询,并提供性能监控工具帮助调优。

安装

本文以在Linux(Rocky)上安装MongoDB为例。

添加MongoDB仓库

因为Linux(Rocky)的官方仓库中没有MongoDB的软件包,所以我们需要先添加MongoDB的官方仓库。

/etc/yum.repos.d/目录下,创建文件mongodb-org-8.0.repo,内容如下:

1
2
3
4
5
6
[mongodb-org-8.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/8/mongodb-org/8.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-8.0.asc

注意:本文以MongoDB-8.0的仓库为例,如果需要安装其他版本的MongoDB,需要修改相应的仓库地址和版本号。

mongodb-org-8.0.repo文件创建完成后,执行如下命令,更新仓库缓存:

1
dnf makecache

安装MongoDB

安装MongoDB,命令如下:

1
dnf install -y mongodb-org

安装完成后,我们可以通过如下命令,查看MongoDB的版本信息:

1
mongod --version

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
db version v8.0.3
Build Info: {
"version": "8.0.3",
"gitVersion": "89d97f2744a2b9851ddfb51bdf22f687562d9b06",
"openSSLVersion": "OpenSSL 3.0.7 1 Nov 2022",
"modules": [],
"allocator": "tcmalloc-google",
"environment": {
"distmod": "rhel93",
"distarch": "x86_64",
"target_arch": "x86_64"
}
}

配置

启动MongoDB服务

先启动MongoDB服务,启动命令:

1
systemctl start mongod

配置管理员账户

执行如下命令,连接MongoDB:

1
mongosh

注意:在早期版本的命令是mongo,在较新版本的命令是mongosh

示例代码:

1
mongosh

运行结果:

1
2
3
4
5
6
7
8
Current Mongosh Log ID: 675298fb8b94b55a9fe94969
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.3.4
Using MongoDB: 8.0.3
Using Mongosh: 2.3.4

【部分运行结果略】

test>

通过use 【数据库】,切换到admin数据库,示例代码:

1
use admin

运行结果:

1
switched to db admin

创建一个名为admin,密码为password的管理员账户,示例代码:

1
db.createUser({user: "admin", pwd: "password", roles: ["root"]})

运行结果:

1
{ ok: 1 }

创建完成后,退出连接,示例代码:

1
exit

开启权限校验

修改/etc/mongod.conf,找到如下内容:

1
#security:

修改为:

1
2
security:
authorization: enabled

重启MongoDB服务,示例代码:

1
systemctl restart mongod

此时,如果我们直接通过mongosh连接,不含有用户名密码等参数,并执行show databases;的话,返回如下,没有权限。

1
MongoServerError[Unauthorized]: Command listDatabases requires authentication

登录命令如下:

1
mongosh -u admin -p password --authenticationDatabase admin

允许远程连接

修改/etc/mongod.conf,,找到如下内容:

1
2
3
4
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.

bindIp: 127.0.0.1修改为bindIp: 0.0.0.0

一些概念

在数据库、表、集合等方面,MongoDB和传统关系型数据库之间的对应关系如下:

RDBMS MongoDB
数据库 database 数据库 database
表 table 集合 collection
行 row 文档 document
列 column 字段 field

新增、更新和删除

查看所有库

两种方式查看所有库:

  • show databases;
  • show dbs;

示例代码:

1
show databases;

运行结果:

1
2
3
admin   132.00 KiB
config 72.00 KiB
local 72.00 KiB

创建数据库

创建数据库:use 【库名】

示例代码:

1
use kaka

运行结果:

1
switched to db kaka

然后我们执行show dbs,会发现居然没有kaka这个库,示例代码:

1
show dbs;

运行结果:

1
2
3
admin   132.00 KiB
config 96.00 KiB
local 72.00 KiB

use代表创建并使用,当库中没有数据时默认不显示这个库。

我们插入一条数据,示例代码:

1
db.users.insertOne({"id":1})

运行结果:

1
2
3
4
{
acknowledged: true,
insertedId: ObjectId('6752b0ef176fc52743e9496b')
}

再看一下,会发现这时候有kaka这个库了。示例代码:

1
show dbs;

运行结果:

1
2
3
4
admin   132.00 KiB
config 96.00 KiB
kaka 8.00 KiB
local 72.00 KiB

删除数据库

删除数据库:db.dropDatabase()

  • 删除的是当前选中的库。
  • 可以通过db命令,查看当前库。

示例代码:

1
db

运行结果:

1
kaka

示例代码:

1
db.dropDatabase();

运行结果:

1
{ ok: 1, dropped: 'kaka' }

集合

查看库中所有集合

两种方式查看库中所有集合:

  • show collections;
  • show tables;

创建集合

创建集合:db.createCollection('【集合名称】', [options])
注意:当集合不存在时,向集合中插入文档也会自动创建该集合。

options可以是如下参数:

  • capped
    布尔
    如果为true,则创建固定大小的集合。当达到最大值时,它会自动覆盖最早的文档。当该值为true时,必须指定size参数。
  • size
    数值
    为固定大小集合指定一个最大值,字节数。
  • max
    数值
    指定固定集合中包含文档的最大数量。
  • cappedsize参数是必须一起指定的;max参数则是可选的,用于进一步限制集合中可以包含的最大文档数量。

示例代码:

1
2
3
4
5
db.createCollection("logs", {
capped: true,
size: 10 * 1024 * 1024, // 10 MB in bytes
max: 500
});

运行结果:

1
{ ok: 1 }

查看集合信息

查看集合信息:db.【集合名称】.stats()

上述命令会返回很多内容,如果我们只关注cappedsizemax,可以做过滤输出:

1
2
3
4
5
6
var stats = db.logs.stats();
printjson({
capped: stats.capped,
size: stats.size,
max: stats.max
});

删除集合

删除集合:db.【集合名称】.drop();

文档

插入文档

插入单条文档

插入单条文档:

1
2
3
4
db.【集合名称】.insertOne(
{ /* 文档的字段和值 */ },
{ /* 可选参数 */}
)

示例代码:

1
2
3
4
db.users.insertOne(
// 要插入的文档
{ "name": "kaka" },
);

插入多条文档

插入多条文档:

1
2
3
4
5
6
7
8
9
db.【集合名称】.insertMany(
[
// 这里放置一个或多个文档对象,每个对象代表一条记录
{ /* 文档1字段和值 */ },
{ /* 文档2字段和值 */ },
// 可以继续添加更多文档...
],
{ /* 可选参数 */ }
)

示例代码:

1
2
3
4
5
6
7
db.user.insertMany(
[
{ name: "Alice", age: 30, occupation: "Engineer" },
{ name: "Bob", age: 25, occupation: "Designer" },
{ name: "Charlie", age: 35, occupation: "Manager" }
]
)

运行结果:

1
2
3
4
5
6
7
8
{
acknowledged: true,
insertedIds: {
'0': ObjectId('675398d1cd1d45b4ffe9496b'),
'1': ObjectId('675398d1cd1d45b4ffe9496c'),
'2': ObjectId('675398d1cd1d45b4ffe9496d')
}
}

可选参数

可选参数主要有如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 写入关注(writeConcern)定义了MongoDB在报告成功之前需要满足的写入确认级别。
writeConcern: {
// 写操作必须被大多数副本集成员确认;也可以是数字,如 1 表示只需主节点确认。
w: "majority",
// 等待写关注满足的最大时间(毫秒),超时后返回错误给客户端。
wtimeout: 5000,
// 如果为 true,则写操作必须等待日志刷新到磁盘上才认为成功。
j: true
},

// ordered 参数决定了文档是否按照给定的顺序进行插入。
// 如果设置为 true(默认),一旦遇到错误,剩余未处理的文档将不会被插入。
// 如果设置为 false,则即使发生错误,MongoDB也会尝试插入所有提供的文档。
ordered: true
  • 其中ordered是插入多条文档参会有的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
db.user.insertMany(
[
{ name: "Alice", age: 30, occupation: "Engineer" },
{ name: "Bob", age: 25, occupation: "Designer" },
{ name: "Charlie", age: 35, occupation: "Manager" }
],
{
writeConcern: { w: 1 }, // 要求确认写操作,默认为 1,即主服务器确认。
ordered: true // 指定是否按顺序写入,默认 true,按顺序写入。
}
)

删除

删除单条文档

删除单条文档:

1
2
3
4
db.【集合名称】.deleteOne(
<query>,
{ /* 可选参数 */ }
);

示例代码:

1
2
// 从 user 集合中删除第一个匹配到的名为 "kaka" 的文档。
db.user.deleteOne({ name: "kaka" });

运行结果:

1
{ acknowledged: true, deletedCount: 1 }

解释说明:deletedCount: 1,表示成功删除了1个文档。

删除多条文档

删除多条文档:

1
2
3
4
5
db.【集合名称】.deleteMany(
// 定义要删除的文档所必须满足的条件。如果传递空文档 `{}`,则匹配所有文档。
<query>,
{ /* 可选参数 */ }
)

示例代码:

1
2
3
4
// 从 user 集合中删除所有年龄大于30岁的文档,并指定写入关注。
db.user.deleteMany(
{ age: { $gt: 30 } }, // 删除所有年龄大于30岁的用户
);

运行结果:

1
{ acknowledged: true, deletedCount: 1 }

可选参数

与插入文档的可选参数几乎一致,不再赘述。

更新

更新单条文档

更新单条文档:

1
2
3
4
5
6
7
db.【集合名称】.updateOne(
// 定义要更新的文档所必须满足的条件。如果传递空文档 `{}`,则匹配所有文档。
<query>,
// 更新操作,指定如何更新匹配的文档。
<update>,
{ /* 可选参数 */ }
)

示例代码:

1
2
3
4
5
// 从 user 集合中找到第一个名为 "kaka" 的文档,并将其年龄更新为31。
db.user.updateOne(
{ name: "kaka" }, // 查询条件,用于定位要更新的文档
{ $set: { age: 31 } } // 更新操作,使用 $set 操作符来修改字段值
);

运行结果:

1
2
3
4
5
6
7
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

解释说明:

  • acknowledged
    • 类型:布尔值
    • 描述:指示操作是否被服务器确认。
      如果为true,则表示客户端收到了来自MongoDB服务器的成功确认;如果为false,则可能意味着使用了writeConcern配置为不需要确认,或者发生了某些错误导致操作未被确认。
  • insertedId
    • 类型:ObjectId或null
    • 描述:当设置了upsert: true并且没有匹配到现有文档时,此字段会包含新插入文档的_id值。
      在上文的该字段为null,这表明并没有发生插入操作,即要么是更新了一个已存在的文档,要么是没有启用upsert选项。
  • matchedCount
    • 类型:数字
    • 描述:表示根据查询条件匹配到的文档数量。
  • modifiedCount
    • 类型:数字
    • 描述:表示实际被修改的文档数量。
  • upsertedCount
    • 类型:数字
    • 描述:表示由 upsert 操作新插入的文档数量。
      如果我们操作启用了upsert选项并且没有找到匹配的文档,那么MongoDB将插入一条新记录,并且这个计数器将增加。

更新多条文档

更新多条文档:

1
2
3
4
5
6
7
db.【集合名称】.updateMany(
// 定义要更新的文档所必须满足的条件。如果传递空文档 `{}`,则匹配所有文档。
<query>,
// 更新操作,指定如何更新匹配的文档。
<update>,
{ /* 可选参数 */ }
)

示例代码:

1
2
3
4
5
6
7
// 将 user 集合中所有年龄大于等于30岁的用户的职业更新为 "Senior Engineer"。
db.user.updateMany(
// 匹配所有年龄大于等于30岁的用户
{ age: { $gte: 30 } },
// 更新职业信息
{ $set: { occupation: "Senior Engineer" } }
);

运行结果:

1
2
3
4
5
6
7
{
acknowledged: true,
insertedId: null,
matchedCount: 2,
modifiedCount: 2,
upsertedCount: 0
}

replaceOne

替换单条文档:

1
2
3
4
5
db.【集合名称】.replaceOne(
<query>,
<replacement>,
{ /* 可选参数 */ }
);

updateOne不同,replaceOne方法用全新的文档替换掉匹配到的第一个文档。

示例代码:

1
2
3
4
5
6
7
// 从 user 集合中找到第一个名为 "kaka" 的文档,并完全替换为新的文档内容。
db.user.replaceOne(
// 查询条件,用于定位要替换的文档
{ name: "kaka" },
// 新的文档内容
{ name: "kaka", age: 32, occupation: "Architect" },
);

运行结果:

1
2
3
4
5
6
7
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

可选参数

有一个参数是之前没有的,upsert

  • upsert参数决定了当没有匹配到文档时是否插入新文档。
  • 如果设置为true,则在没有匹配文档的情况下会创建一个新文档。
  • 默认为false,表示不插入新文档。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将 user 集合中所有年龄大于等于30岁的用户的职业更新为 "Senior Engineer"。
db.user.updateMany(
{ age: { $gte: 30 } }, // 匹配所有年龄大于等于30岁的用户
{ $set: { occupation: "Senior Engineer" } }, // 更新职业信息
{
writeConcern: {
w: "majority",
wtimeout: 5000,
j: true
},
upsert: false // 不插入新文档
}
);

文档查询

语法

文档查询语法

1
db.【集合名称】.find(query, projection);
  • query
    • 类型:文档(Document)
    • 作用:可选参数,使用查询操作符来指定筛选条件。如果不提供查询条件,则默认匹配所有文档。
  • projection
    • 类型:文档(Document)
    • 作用:可选参数,使用投影操作符来指定返回哪些字段。如果省略此参数,默认会返回文档中的所有字段。

格式化输出

为了使查询结果更易于阅读,MongoDB提供了pretty()方法,可以格式化输出,使文档结构更加清晰易读。

1
db.【集合名称】.find().pretty();

注意,虽然pretty()方法提高了可读性,但在处理大量数据时可能会导致性能下降,因为需要额外的时间来进行格式化。因此,在生产环境中进行大数据量查询时应谨慎使用。

示例代码

1
2
3
4
5
6
7
8
9
10
11
// 返回集合中的所有文档,结果以非格式化形式显示
db.user.find();

// 使用查询条件返回特定文档,例如查找名为 "kaka" 的用户
db.user.find({ name: "kaka" });

// 使用投影返回特定字段,例如只返回用户的姓名和年龄
db.user.find({}, { name: 1, age: 1, _id: 0 });

// 结合 pretty()方法以格式化的方式显示查询结果
db.user.find({ name: "kaka" }).pretty();

比较运算符

大小关系比较

操作 格式 范例 RDBMS中的类似语句
等于 {<key>:<value>} db.col.find({"name":"Kaka"}).pretty() where name = 'Kaka'
小于 {<key>:{$lt:<value>}} db.col.find({"age":{$lt:50}}).pretty() where age < 50
小于或等于 {<key>:{$lte:<value>}} db.col.find({"age":{$lte:50}}).pretty() where age <= 50
大于 {<key>:{$gt:<value>}} db.col.find({"age":{$gt:50}}).pretty() where age > 50
大于或等于 {<key>:{$gte:<value>}} db.col.find({"age":{$gte:50}}).pretty() where age >= 50
不等于 {<key>:{$ne:<value>}} db.col.find({"age":{$ne:50}}).pretty() where age != 50

数组中查询

假设,存在数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{ _id: 1, age: 29, likes: [ '阅读', '旅行', '电影' ], name: '李华' },
{ _id: 2, age: 35, likes: [ '音乐', '摄影', '烹饪' ], name: '王伟' },
{
_id: 3,
age: 40,
likes: [
{ activity: '阅读', rating: 5 },
{ activity: '旅行', rating: 4 },
{ activity: '电影', rating: 5 }
],
name: '张丽'
}
]

查询包含特定元素的数组

查询包含特定元素的数组的文档,可以直接在查询条件中指定该元素。示例代码:

1
db.user.find({ likes: "阅读" });

解释说明:此查询返回所有likes数组中至少包含"阅读"这个值的文档。

按数组长度查询

可以使用$size操作符,根据数组的长度来筛选文档。示例代码:

1
db.user.find({ likes: { $size: 3 } });

解释说明:此查询返回likes数组正好有三个元素的文档。

其他数组查询操作符

  • $all:匹配数组中包含所有给定值的文档。
    示例代码:
    1
    2
    // 查找 likes 数组中同时包含 "阅读" 和 "电影" 的文档
    db.user.find({ likes: { $all: ["阅读", "电影"] } });
  • $elemMatch:匹配数组中至少有一个元素同时满足多个条件的文档。
    示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 查找 likes 数组中既有评分等于 5 且活动为 "阅读" 的用户
    db.user.find({
    likes: {
    $elemMatch: {
    activity: "阅读",
    rating: 5
    }
    }
    });
  • $in$nin:分别用于匹配数组中包含或不包含指定值的文档。
    示例代码:
    1
    2
    3
    4
    5
    // 查找 likes 数组中包含 "阅读" 或 "电影" 的文档
    db.user.find({ likes: { $in: ["阅读", "电影"] } });

    // 查找 likes 数组中既不包含 "阅读" 也不包含 "电影" 的文档
    db.user.find({ likes: { $nin: ["阅读", "电影"] } });

模糊查询

正则表达式

在关系型数据库中,我们"LIKE"语句来执行模糊匹配查询。
MongoDB提供了类似的功能,但是通过正则表达式(Regular Expressions,RegEx)来实现的。

例子

假设存在一批数据,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建 products 集合并插入测试数据
db.products.insertMany([
{ "_id": 1, "name": "Laptop", "description": "A powerful laptop for developers" },
{ "_id": 2, "name": "Tablet", "description": "An affordable tablet for entertainment" },
{ "_id": 3, "name": "Smartphone", "description": "The latest smartphone with advanced features" },
{ "_id": 4, "name": "Headphones", "description": "High-quality wireless headphones" },
{ "_id": 5, "name": "Keyboard", "description": "Ergonomic keyboard designed for comfort" },
{ "_id": 6, "name": "Monitor", "description": "Wide monitor suitable for gaming and work" },
// 注意这里的 name 是小写的
{ "_id": 7, "name": "laptop", "description": "Another laptop, but this one is smaller." },
// 注意这里的 name 是小写的
{ "_id": 8, "name": "tablet", "description": "A compact tablet perfect for reading." }
]);
  • 查询包含特定子串的商品,区分大小写,示例代码:
    1
    2
    // 查找 description 中包含 "powerful" 的商品
    db.products.find({ description: /powerful/ });
    解释说明:这条命令返回description字段中包含"powerful"的商品,并且是区分大小写的。
  • 忽略大小写的模糊查询,/i标志指定了忽略大小写,示例代码:
    1
    db.products.find({ name: /laptop/i });
    解释说明:这条命令返回所有name字段中包含"laptop"的商品,/i标志指定了忽略大小写。
  • 查询以特定子串开头的商品,示例代码:
    1
    2
    // 查找 name 以 "Smart" 开头的商品
    db.products.find({ name: /^Smart/ });
    解释说明:这条命令返回name字段以"Smart"开头的商品。
  • 查询以特定子串结尾的商品,示例代码:
    1
    2
    // 查找 description 以 "work" 结尾的商品
    db.products.find({ description: /work$/ });
    解释说明:这条命令返回description字段以"work"结尾的商品。

多个条件组合的模糊查询

我们还可以结合其他查询条件与正则表达式一起使用,以实现更精细的筛选。示例代码:

1
2
// 查找 description 包含 "wireless" 且 name 包含 "headphones" 的商品,忽略大小写
db.products.find({ description: /wireless/i, name: /headphones/i });

逻辑运算符

假设存在一批数据如下:

1
2
3
4
5
6
7
8
9
10
11
// 创建 books 集合并插入测试数据
db.books.insertMany([
{ "_id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "genre": "Classic" },
{ "_id": 2, "title": "1984", "author": "George Orwell", "year": 1949, "genre": "Dystopian" },
{ "_id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "genre": "Classic" },
{ "_id": 4, "title": "Brave New World", "author": "Aldous Huxley", "year": 1932, "genre": "Dystopian" },
{ "_id": 5, "title": "Pride and Prejudice", "author": "Jane Austen", "year": 1813, "genre": "Romance" },
{ "_id": 6, "title": "The Catcher in the Rye", "author": "J.D. Salinger", "year": 1951, "genre": "Classic" },
{ "_id": 7, "title": "Animal Farm", "author": "George Orwell", "year": 1945, "genre": "Allegorical" },
{ "_id": 8, "title": "Fahrenheit 451", "author": "Ray Bradbury", "year": 1953, "genre": "Dystopian" }
]);

AND运算符

我们可以在同一查询条件中指定多个键值对,这些键值对条件将通过逻辑AND组合起来的。

查找出版年份为1949年且类型为"Dystopian"的书籍,示例代码:

1
2
3
4
5
// 查找 year = 1949 并且 genre = "Dystopian" 的书籍
db.books.find({
year: 1949,
genre: "Dystopian"
});

OR运算符

$or操作符使用方法:将不同的条件放入一个数组中,并将其作为$or的值传递给查询条件。

查找作者为"George Orwell"或类型为"Romance"的书籍,示例代码:

1
2
3
4
5
6
7
// 查找 author = "George Orwell" 或 genre = "Romance" 的书籍
db.books.find({
$or: [
{ author: "George Orwell" },
{ genre: "Romance" }
]
});

结合AND和OR运算符

查找出版年份大于1950年,并且作者为"George Orwell"或类型为"Dystopian"的书籍,示例代码:

1
2
3
4
5
6
7
8
// 查找 year > 1950 并且 (author = "George Orwell" 或 genre = "Dystopian") 的书籍
db.books.find({
year: { $gt: 1950 },
$or: [
{ author: "George Orwell" },
{ genre: "Dystopian" }
]
});

排序

MongoDB提供了sort()方法指定一个或多个字段,并提供了升序(1)和降序(-1)两种排序方式。

示例代码:

1
2
// 按 category 升序, publishedDate 降序排序
db.articles.find().sort({ category: 1, publishedDate: -1 });

分页

MongoDB提供了skip()limit()方法来实现分页,类似于SQL中的LIMIT语句。

示例代码:

1
2
// 获取第 2 页的数据,每页 2 条记录,按 publishedDate 降序排序
db.articles.find().sort({ publishedDate: -1 }).skip(2).limit(2);

统计总条数

MongoDB提供了countDocuments()方法用于统计符合查询条件的文档数量。

示例代码:

1
2
3
4
5
// 统计所有文档的数量
db.articles.countDocuments();

// 统计 category 为 "Database" 的文档数量
db.articles.countDocuments({ "category": "Database" });

去重

MongoDB提供了distinct()方法用于返回集合中某个字段的所有不同值,类似于SQL中的SELECT DISTINCT语句。

示例代码:

1
2
// 获取 author 字段的所有不同值
db.articles.distinct('author');

指定返回字段

投影的基本规则

有时我们只需要查询文档中的某些特定字段,而不是整个文档。MongoDB通过投影(projection)功能来实现这一点,允许我们在查询时指定哪些字段应该被包含或排除。

基本规则:

  • 包含某些字段
    如果我们想指定哪些字段应该被包含在结果中,可以将这些字段设置为1
  • 排除某些字段
    如果我们想指定哪些字段不应该出现在结果中,可以将这些字段设置为0
  • _id字段的特殊性
    默认情况下,MongoDB总是会返回_id字段,除非我们明确地将其设置为0

投影的注意事项

  • 不能同时使用10
    在一个投影对象中,不能同时使用10来指定字段。
    唯一的例外是_id字段,可以与其他字段一起被指定为0
  • 包含所有其他字段
    如果指定了一个或多个字段为1,则只有这些字段会被返回,而其他所有字段将被排除。
  • 排除所有其他字段
    如果指定了一个或多个字段为0,那么这些字段将被排除,而其他所有字段将被包含。

例子

1
2
// 只返回 title 和 category 字段,不返回 _id 字段
db.articles.find({}, { title: 1, category: 1, _id: 0 });
1
2
// 返回所有字段,但不返回 publishedDate 字段
db.articles.find({}, { publishedDate: 0 });

如果同时指定10,会报错。示例代码:

1
2
// 返回 title 和 category 字段,不返回 author 字段和 _id 字段
db.articles.find({}, { title: 1, category: 1, author: 0, _id: 0 });

运行结果:

1
MongoServerError[Location31254]: Cannot do exclusion on field author in inclusion projection

$type

作用

$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。

MongoDB中可以使用的类型如下表所示:

类型名称 数值代码 字符串别名 注意
Double 1 "double"
String 2 "string"
Object 3 "object"
Array 4 "array"
Binary Data 5 "binData"
Undefined 6 "undefined" 已废弃。
ObjectId 7 "objectId"
Boolean 8 "bool"
Date 9 "date"
Null 10 "null"
Regular Expression 11 "regex"
DBPointer 12 "dbPointer" 已弃用。
JavaScript 13 "javascript"
Symbol 14 "symbol" 已废弃,建议使用字符串。
Int32 (32-bit Integer) 16 "int"
Timestamp 17 "timestamp"
Int64 (64-bit Integer) 18 "long"
Decimal128 19 "decimal"
MinKey -1 "minKey"
MaxKey 127 "maxKey"

注意事项:

  • Undefined、DBPointer和Symbol类型,这些类型在现代MongoDB中已经不推荐使用或已被废弃。
  • JavaScript类型,通常不建议直接在数据库中存储JavaScript代码,除非有特殊需求。

例子

例如,要查找所有price字段为64位浮点数(Double)的文档:

1
db.collection.find({ price: { $type: "double" } });

或者使用类型代码:

1
db.collection.find({ price: { $type: 1 } });

聚合

例子

假设,存在一批数据如下:

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
48
49
50
51
// 创建 events 集合并插入测试数据
db.events.insertMany([
{
name: "Tech Conference",
organizer: "Tech Innovators",
attendees: 500,
eventType: "conference",
location: "San Francisco",
cost: 150.00
},
{
name: "Art Exhibition",
organizer: "Art Lovers",
attendees: 200,
eventType: "exhibition",
location: "New York",
cost: 30.00
},
{
name: "Music Festival",
organizer: "Music Mania",
attendees: 800,
eventType: "festival",
location: "Los Angeles",
cost: 75.00
},
{
name: "Science Symposium",
organizer: "Science Society",
attendees: 300,
eventType: "symposium",
location: "Chicago",
cost: 100.00
},
{
name: "Film Premiere",
organizer: "Cinema World",
attendees: 150,
eventType: "premiere",
location: "Los Angeles",
cost: 50.00
},
{
name: "Cooking Workshop",
organizer: "Gourmet Kitchen",
attendees: 40,
eventType: "workshop",
location: "San Francisco",
cost: 25.00
}
]);

统计每个组织者的活动数量

示例代码:

1
2
3
4
5
6
7
8
9
10
db.events.aggregate([
{
$group: {
// 按照 organizer 字段分组
_id: "$organizer",
// 计算每个分组中活动的数量
totalEvents: { $sum: 1 }
}
}
]);

运行结果:

1
2
3
4
5
6
7
8
[
{ _id: 'Music Mania', totalEvents: 1 },
{ _id: 'Gourmet Kitchen', totalEvents: 1 },
{ _id: 'Art Lovers', totalEvents: 1 },
{ _id: 'Science Society', totalEvents: 1 },
{ _id: 'Tech Innovators', totalEvents: 1 },
{ _id: 'Cinema World', totalEvents: 1 }
]

解释说明:

  • $group:根据organizer字段对活动进行分组。
  • totalEvents:计算每个组织者举办的活动总数。

计算每个城市的总参与人数

示例代码:

1
2
3
4
5
6
7
8
9
10
db.events.aggregate([
{
$group: {
// 按照 location 字段分组
_id: "$location",
// 计算每个城市所有活动的总参与人数
totalAttendees: { $sum: "$attendees" }
}
}
]);

运行结果:

1
2
3
4
5
6
[
{ _id: 'Los Angeles', totalAttendees: 950 },
{ _id: 'San Francisco', totalAttendees: 540 },
{ _id: 'New York', totalAttendees: 200 },
{ _id: 'Chicago', totalAttendees: 300 }
]

解释说明:

  • $group:根据location字段对活动进行分组。
  • totalAttendees:计算每个城市所有活动的总参与人数。

找出费用超过一定金额的活动

示例代码:

1
2
3
4
5
6
7
8
9
10
db.events.aggregate([
{
// 选择费用大于 50.00 的活动
$match: { cost: { $gt: 50.00 } }
},
{
// 按费用降序排列
$sort: { cost: -1 }
}
]);

解释说明:

  • $match:筛选出费用大于50.00的活动。
  • $sort:将结果按照费用降序排列。

限制返回的结果数量

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
db.events.aggregate([
{
$match: { cost: { $gt: 50.00 } }
},
{
$sort: { cost: -1 }
},
{
// 仅返回前 3 个满足条件的活动
$limit: 3
}
]);

解释说明:

  • $limit:确保只返回前3个满足条件的活动。

多阶段聚合查询

找到每个城市中最贵的活动,并且只返回活动名称、费用和城市信息。示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
db.events.aggregate([
{
$group: {
// 按照 location 字段分组
_id: "$location",
// 找到每个城市最贵的活动费用
maxCostEvent: { $max: "$cost" },
// 保留最贵活动的完整信息
eventDetails: { $first: "$$ROOT" }
}
},
{
$project: {
// 不返回 _id 字段
_id: 0,
// 返回活动名称
eventName: "$eventDetails.name",
// 返回城市名
city: "$_id",
// 返回最高费用
highestCost: "$maxCostEvent"
}
}
]);

解释说明:

  • $group:根据location字段对活动进行分组,并找到每个城市中最贵的活动。
  • $project:重新构建输出文档,只返回活动名称、城市名和最高费用。

常见聚合表达式

表达式 描述 实例
$sum 计算总和。 db.events.aggregate([{$group : {_id : "$organizer", totalLikes : {$sum : "$likes"}}}])
$avg 计算平均值。 db.events.aggregate([{$group : {_id : "$organizer", avgCost : {$avg : "$cost"}}}])
$min 获取集合中所有文档对应值的最小值。 db.events.aggregate([{$group : {_id : "$location", minAttendees : {$min : "$attendees"}}}])
$max 获取集合中所有文档对应值的最大值。 db.events.aggregate([{$group : {_id : "$location", maxCost : {$max : "$cost"}}}])
$push 将值加入一个数组中,不会判断是否有重复的值。 db.events.aggregate([{$group : {_id : "$organizer", eventNames : {$push: "$name"}}}])
$addToSet 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 db.events.aggregate([{$group : {_id : "$organizer", uniqueLocations : {$addToSet : "$location"}}}])
$first 根据资源文档的排序获取第一个文档数据。 db.events.aggregate([{$sort: {date: 1}}, {$group : {_id : "$organizer", firstEvent : {$first : "$name"}}}])
$last 根据资源文档的排序获取最后一个文档数据。 db.events.aggregate([{$sort: {date: -1}}, {$group : {_id : "$organizer", lastEvent : {$last : "$name"}}}])

解释说明:

  • $sum:用于对数值字段进行累加,可以是固定的数值(如1)或文档中的字段值。
  • $avg:计算数值字段的平均值。
  • $min$max:分别找出指定字段中的最小值和最大值。
  • $push:将指定字段的值添加到数组中,允许重复值。
  • $addToSet:类似于$push,但会确保数组中的值唯一。
  • $first$last:根据文档的排序顺序,返回分组内第一个或最后一个文档的指定字段值。需要注意的是,在使用这两个操作符时,通常需要先通过 $sort阶段来确定排序规则。

索引

操作索引

创建索引

创建索引:

1
db.【集合名称】.createIndex(keys, options)

解释说明:

  • keys:一个对象,其键为要索引的字段名,值为升序(1)或降序(-1)。
    1
    db.collection.createIndex({ "title": 1, "description": -1 })
  • options:一个可选的对象,用于指定索引的各种属性。

options的可选类型有:

参数 类型 描述
background Boolean 是否以后台方式创建索引,默认为 false
unique Boolean 是否创建唯一索引,默认为 false
name String 指定索引的名称。如果不指定,MongoDB 将自动生成一个名称。
sparse Boolean 是否对文档中不存在的字段不启用索引,默认为 false
expireAfterSeconds Integer 设置 TTL(Time To Live)索引,单位为秒。
v Integer 索引版本号,默认取决于 mongod 版本。
weights Document 文本索引的权重,数值范围在 1 到 99,999 之间。
default_language String 文本索引的语言,默认为英语。
language_override String 文档中覆盖默认语言的字段名,默认为language

查看索引

查看索引,getIndexes()

1
db.【集合名称】.getIndexes()

查看索引占用空间大小

查看索引占用空间大小,totalIndexSize()

1
db.【集合名称】.totalIndexSize()

删除索引

删除所有索引

删除所有索引,dropIndexes()

1
db.【集合名称】.dropIndexes()

删除特定索引

删除特定索引,dropIndex(),传入索引名称:

1
db.集合名称.dropIndex("索引名称")

索引分类

  1. 单字段索引
    仅对一个字段创建索引。
  2. 复合索引
    对多个字段创建索引,通常用于支持多字段查询条件。
    遵循最左前缀法则(最左前缀原则、最左前缀匹配原则)。
    (关于最左前缀匹配原则,可以参考《MySQL从入门到实践:5.索引和优化》)
  3. TTL(Time to Live)索引
    用于自动删除过期文档,常用于日志或临时数据存储。
    即,在创建索引的options参数中,指定expireAfterSeconds属性。示例代码:
    1
    db.collection.createIndex({ "createdAt": 1 }, { expireAfterSeconds: 3600 })
  4. 文本索引
    支持全文搜索功能,特别适合处理大量的文本内容。示例代码:
    1
    db.collection.createIndex({ "content": "text" })
  5. 地理空间索引
    支持地理位置相关的查询,如查找附近的地点。示例代码:
    1
    db.collection.createIndex({ "location": "2dsphere" })

关于TTL索引,需要说明的是:
TTL索引在MongoDB中用于自动删除过期的文档,而不是仅仅清理索引条目。

删除过程如下:

  • 后台线程处理:MongoDB使用一个后台线程每分钟扫描一次带有TTL索引的集合,查找并删除那些超过生存时间(即expireAfterSeconds所指定的时间)的文档。这意味着实际删除操作可能发生在超时之后的一分钟内。
  • 文档删除:一旦文档被确定为过期,整个文档将会从集合中被物理删除,不仅仅是对应的索引条目。因此,所有与该文档相关的数据都会被清除,包括任何其他普通索引中的引用。

注意事项:

  • 精确度:由于后台线程的周期性检查机制,TTL删除并不是即时发生的;它可能会有一定的延迟,通常不超过一分钟。
  • 性能影响:频繁的大规模删除操作可能会对数据库性能产生影响,尤其是在高并发环境下。因此,在设计TTL索引时应考虑到这一点,并进行适当的测试和监控。
  • 适用场景:TTL索引非常适合用于日志、会话管理和临时数据存储等需要自动清理过期数据的应用场景。

Java操作

原生

依赖

需要三个依赖,分别是:

  • org.mongodb.mongodb-driver-core
  • org.mongodb.bson
  • org.mongodb.mongodb-driver-sync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-core -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>5.2.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mongodb/bson -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
<version>5.2.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-sync -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>5.2.1</version>
</dependency>

在有些资料中,没有专门强调需要org.mongodb.mongodb-driver-coreorg.mongodb.bson

例子

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.kakawanyifan;

import com.mongodb.client.*;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.util.Arrays;
import java.util.List;

public class Demo {
private static MongoCollection<Document> collection;

public static void main(String[] args) {
// 创建MongoClient实例
String connectionString = "mongodb://admin:password@192.168.13.157:27017/testdb?authSource=admin";
MongoClient mongoClient = MongoClients.create(connectionString);

// 获取数据库实例
MongoDatabase database = mongoClient.getDatabase("testdb");

// 获取或创建集合
collection = database.getCollection("users");

try {
// 插入文档
insertDocuments();

// 查询所有文档
findAllDocuments();

// 更新文档
updateDocument();

// 再次查询所有文档
findAllDocuments();

// 删除文档
deleteDocument();

// 最后查询所有文档
findAllDocuments();

// 执行聚合操作
aggregateDocuments();
} finally {
// 关闭客户端
mongoClient.close();
}
}

private static void insertDocuments() {
Document doc1 = new Document("name", "Alice")
.append("age", 30)
.append("address", "Wonderland");
Document doc2 = new Document("name", "Bob")
.append("age", 25)
.append("address", "Builder Street");

collection.insertMany(Arrays.asList(doc1, doc2));
System.out.println("Inserted documents.");
}

private static void findAllDocuments() {
FindIterable<Document> docs = collection.find();
MongoCursor<Document> cursor = docs.iterator();

System.out.println("All documents in the 'users' collection:");
while (cursor.hasNext()) {
System.out.println(cursor.next().toJson());
}
System.out.println();
}

private static void updateDocument() {
Bson filter = Filters.eq("name", "Alice");
Bson updateOperationDocument = Updates.set("age", 31);

collection.updateOne(filter, updateOperationDocument);
System.out.println("Updated document where name is 'Alice'.");
}

private static void deleteDocument() {
Bson filter = Filters.eq("name", "Bob");

collection.deleteOne(filter);
System.out.println("Deleted document where name is 'Bob'.");
}

private static void aggregateDocuments() {
List<Bson> pipeline = Arrays.asList(
Aggregates.match(Filters.gte("age", 30)),
Aggregates.group("$address", Accumulators.sum("count", 1))
);

collection.aggregate(pipeline).forEach(doc -> System.out.println(doc.toJson()));
System.out.println("Aggregated documents by address and counted users over 30 years old.");
}

}

基于SpringBoot

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

MongoRepository

application.properties

1
spring.data.mongodb.uri=mongodb://admin:password@192.168.13.157:27017/testdb?authSource=admin

User

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
package com.kakawanyifan;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.util.Date;

@Data
@Document("users")
public class User {

@Id
private String id;
private String email;
private String name;
private Role role;
private String description;
@Field("created_at")
private Date createdAt;
@Field("updated_at")
private Date updatedAt;
private Boolean deleted;

public enum Role {
ADMIN,
EDITOR,
VIEWER
}
}

解释说明:

  • 使用@Document注解指定了对应的MongoDB集合为users
  • 对主键字段使用了@Id注解。
  • 与MongoDB集合中命名不一致的属性使用了@Field注解指定了实际的字段名。
  • 使用Lombok的@Data注解自动生成了Setters和Getters。

UserRepository

创建一个对应的Repository接口UserRepository,并将其扩展MongoRepository接口。

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface UserRepository extends MongoRepository<User, String> {

List<User> findByName(String name);

}

测试类

针对UserRepository编写一个单元测试类来对其提供的增、删、改、查方法进行测试。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.kakawanyifan;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
class ApplicationTests {

@Autowired
private UserRepository userRepository;

@Test
public void testCount() {
long count = userRepository.count();
assertEquals(3, count);
}

@Test
public void testFindAll() {
List<User> users = userRepository.findAll();

assertEquals(3, users.size());
}

@Test
public void testFindById() {
Optional<User> optional = userRepository.findById("67554f2a1e935d4c15e9497c");

assertTrue(optional.isPresent());
assertEquals("Larry", optional.get().getName());
}

@Test
public void testFindByName() {
List<User> users = userRepository.findByName("Larry");

assertEquals(1, users.size());
assertEquals("Larry", users.get(0).getName());
}

@Test
public void testSave() {
Date now = new Date();

User user = new User();
user.setEmail("linda@linda.com");
user.setName("Linda");
user.setRole(User.Role.EDITOR);
user.setDescription("I am Linda");
user.setCreatedAt(now);
user.setUpdatedAt(now);
user.setDeleted(false);

// save
userRepository.save(user);
}

@Test
public void testUpdate() {
User user = userRepository.findById("67554f2a1e935d4c15e9497d").get();
user.setName("Larry2");

userRepository.save(user);
}

@Test
public void testDelete() {
userRepository.deleteById("67554f2a1e935d4c15e9497e");
}

}

MongoTemplate

除了通过定义Repository接口来对MongoDB进行增、删、改、查操作,我们还可以使用更加灵活的MongoTemplate来对MongoDB进行操作。

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
package com.kakawanyifan;

import com.mongodb.client.result.UpdateResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class MongoTemplateTest {

@Autowired
private MongoTemplate mongoTemplate;

@Test
public void testFindAll() {
List<User> users = mongoTemplate.findAll(User.class);
assertFalse(users.isEmpty());
}

@Test
public void testFindByName() {
Query query = new Query(Criteria.where("name").is("Jacky"));
User user = mongoTemplate.findOne(query, User.class);

assertNotNull(user);
assertEquals("jacky@jacky.com", user.getEmail());
}

@Test
public void testUpdateEmailByName() {
Query query = new Query(Criteria.where("name").is("Lucy"));
Update update = new Update().set("email", "lucylucy@lucy.com");

UpdateResult result = mongoTemplate.updateMulti(query, update, User.class);
assertEquals(1, result.getModifiedCount());
}

}

Python操作

pymongo

安装pymongo

官方文档:https://pymongo.readthedocs.io/en/stable/
PyPi地址:https://pypi.org/project/pymongo
安装命令:pip install pymongo

例子

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pymongo import MongoClient


def main():
# 创建MongoClient实例,连接本地MongoDB服务器,默认端口27017
client = MongoClient('mongodb://admin:password@192.168.13.157:27017/testdb?authSource=admin')

try:
# 获取数据库实例
db = client['testdb']

# 获取或创建集合
collection = db['users']

# 插入文档
insert_documents(collection)

# 查询所有文档
find_all_documents(collection)

# 更新文档
update_document(collection)

# 再次查询所有文档
find_all_documents(collection)

# 删除文档
delete_document(collection)

# 最后查询所有文档
find_all_documents(collection)

# 执行聚合操作
aggregate_documents(collection)

finally:
# 关闭客户端
client.close()


def insert_documents(collection):
doc1 = {
"name": "Alice",
"age": 30,
"address": "Wonderland"
}
doc2 = {
"name": "Bob",
"age": 25,
"address": "Builder Street"
}

result = collection.insert_many([doc1, doc2])
print(f"Inserted documents with ids: {result.inserted_ids}")


def find_all_documents(collection):
print("All documents in the 'users' collection:")
for doc in collection.find():
print(doc)
print()


def update_document(collection):
result = collection.update_one(
{"name": "Alice"},
{"$set": {"age": 31}}
)
print(f"Matched {result.matched_count} document(s), modified {result.modified_count} document(s).")


def delete_document(collection):
result = collection.delete_one({"name": "Bob"})
print(f"Deleted {result.deleted_count} document(s) where name is 'Bob'.")


def aggregate_documents(collection):
pipeline = [
{"$match": {"age": {"$gte": 30}}},
{"$group": {"_id": "$address", "count": {"$sum": 1}}}
]

print("Aggregated documents by address and counted users over 30 years old:")
for doc in collection.aggregate(pipeline):
print(doc)
print()


if __name__ == "__main__":
main()
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12601
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板