意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

MongoDB查询分组并获取TopN数据

来源:恒创科技 编辑:恒创科技编辑部
2023-12-05 17:32:59

分组并获取每个分组中Top N个数据的需求在实际开发的过程中经常会遇到。例如,购物网站中经常会遇到的展示一个店铺列表,每个店铺列表中带有多个该店铺的产品信息。当然,展示店铺列表并分别去获取店铺指定数量的产品是个最为简单的做法,但需要消耗大量的资源。

在本文中,我们将会以一个简单的例子展示在MongoDB中实现分组并获取Top N个数据的实现方法。

示例
首先,我们在MongoDB中有一个用户信息的数据集合user,它存有下面的几条数据。


MongoDB查询分组并获取TopN数据

[
{ "name": "刘大", "age": 28, "status": "active" },
{ "name": "陈二", "age": 25, "status": "active" },
{ "name": "张三", "age": 25, "status": "active" },
{ "name": "李四", "age": 25, "status": "active" },
{ "name": "王五", "age": 23, "status": "active" },
{ "name": "赵六", "age": 23, "status": "active" },
{ "name": "孙七", "age": 23, "status": "inactive" },
{ "name": "周八", "age": 23, "status": "active" }
]

在以上数据的基础上,我们准备在每个年龄抽取前两个(以先添加的文档为准)状态为active的人,并以年龄从小到大的形式输出分组。

首先,我们使用$match运算符进行了筛选,去除了状态不为active的文档。根据上面的要求,我们需要按年龄从小到大的形式排序,即使age按升序的形式排序(升序在MongoDB中以1表示)。另外,为了实现每个分组都能取到最先添加的两个文档,我们也增加了基于createdAt的升序排序。age的排序也可以在$group后执行,但在这里我们直接与时间排序合并在一起执行。

在筛选并排序后,我们需要使用$group运算符根据指定的字段进行分组。根据要求,我们需要使用age作为分组的依据,所以在实现中我们将_id设置为$age。在分组中,我们希望获取到各分组中的数组,所以使用了$push运算,将各文档(使用$$ROOT代表根文档)保存到products中。完成分组后,每个分组中的products保存了该分组所有的文档,为了实现获取TopN个元素,我们需要在$project中使用$slice限制返回的文档个数。

db.user.aggregate([
{
$match: {
status: "active",
},
},
{
$sort: {
age: 1,
createdAt: 1,
},
},
{
$group: {
_id: "$age",
persons: {
$push: "$$ROOT",
},
},
},
{
$project: {
_id: 0,
age: "$_id",
persons: {
$slice: ["$persons", 2],
},
},
},
])

$slice接收2-3个参数,第一个参数表示数组,第二个表示截取位置(存在第三个参数的情况)或截取个数(正数从前往后截取,负数从后往前截取), 第三个参数表示截取个数

执行该查询,可以得到下面的返回结果:

[
{
age: 23,
persons: [
{ name: "王五", age: 23, status: "active" },
{ name: "赵六", age: 23, status: "active" },
],
},
{
age: 25,
persons: [
{ name: "陈二", age: 25, status: "active" },
{ name: "张三", age: 25, status: "active" },
],
},
{
age: 28,
persons: [{ name: "刘大", age: 28, status: "active" }],
},
]

不分组返回结果
上面的输出结果中仍保持着分组的形式,如果需要将结果转换为文档的数组,可以另外使用$unwind以及$replaceRoot运算符。例如下面的例子:

db.user.aggregate([
// $match, $sort, $group, $project
{
$unwind: "$persons",
},
{
$replaceRoot: {
newRoot: "$persons",
},
},
])

该查询执行后得到的结果为:

[
{ name: "王五", age: 23, status: "active" },
{ name: "赵六", age: 23, status: "active" },
{ name: "陈二", age: 25, status: "active" },
{ name: "张三", age: 25, status: "active" },
{ name: "刘大", age: 28, status: "active" },
]

$slice查询修饰符
$slice元素会限制数组元素的个数

查询输出数组前三个:

db.movies.find({title:"Youth Without Youth"},{languages:{$slice:3}}).pretty();

查询输出数组后两个

db.movies.find({title:"Youth Without Youth"},{languages:{$slice:-2}}).pretty();

查询输出从数组第四个开始,输出前三个

db.movies.find({title:"Youth Without Youth"},{languages:{$slice:[3,3]}}).pretty();

查询输出从数组倒数第五个开始,输出前四个

db.movies.find({title:"Youth Without Youth"},{languages:{$slice:[-5,4]}}).pretty();

$slice:[a,b]
a是正数时,起始位置是a+1,
a是负数时,起始位置a(倒数数组)
b是输出个数



上一篇: mysql直接拷贝data目录下数据库源文件还原数据库方法 下一篇: JavaScript单线程和任务队列是什么