MongoDB聚合运算符:$topN
文章目录
- [MongoDB聚合运算符:topN](#MongoDB聚合运算符:topN)
$topN
聚合运算符返回分组中指定顺序的最前面 n
个元素,如果分组中的元素数量小于 n
,则返回分组的全部元素。从MongoDB5.2开始支持。
语法
js
{
$topN:
{
n: <expression>,
sortBy: { <field1>: <sort order>, <field2>: <sort order> ... },
output: <expression>
}
}
n
用于限制每组结果的数量,必须是正整数表达式,要么是常数,要么取决于$group
的_id
值sortBy
制定返回结果的顺序,语法类似于$sort
output
指定分组元素输出的内容,可以是任何合法的表达式。
用法
$topN
不支持作为聚合表达式。$topN
只支持作为window 操作符
。- 聚合管道调用
$topN
受100M的限制,如果单组超过这一限制将报错。
关于null和缺失值的处理
$topN
不会过滤掉空值$topN
会将缺失值转换为null
js
db.aggregate( [
{
$documents: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: 2 },
{ playerId: "PlayerC", gameId: "G1", score: 3 },
{ playerId: "PlayerD", gameId: "G1"},
{ playerId: "PlayerE", gameId: "G1", score: null }
]
},
{
$group:
{
_id: "$gameId",
playerId:
{
$topN:
{
output: [ "$playerId", "$score" ],
sortBy: { "score": 1 },
n: 3
}
}
}
}
] )
在这个例子中:
- 使用
$documents
阶段创建了一些字面量(常量)文档,包含了选手的得分 $group
阶段根据gameId
对文档进行了分组,显然文档中的gameId
都是G1
PlayerD
的得分缺失,PlayerE
的得分为null
,他们的得分都会被当做null
处理playerId
字段和score
字段被指定为输出:["$playerId"," $score"]
,以数组的形式返回sortBy: { "score": 1 }
指定了排序的方式,空值被排在最前面,返回playerId
数组
如下:
js
[
{
_id: 'G1',
playerId: [ [ 'PlayerD', null ], [ 'PlayerE', null ], [ 'PlayerA', 1 ] ]
}
]
BSON数据类型排序
当不同类型排序是,使用BSON数据类型的顺序进行排序:
- 当进行正序排序时(由小到大),字符串的优先级在数值之前
- 当进行逆序排序时(由大到小),字符串的优先级在数值之前
下面的例子中包含了字符串和数值类型:
js
db.aggregate( [
{
$documents: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: "2" },
{ playerId: "PlayerC", gameId: "G1", score: "" }
]
},
{
$group:
{
_id: "$gameId",
playerId: {
$topN:
{
output: ["$playerId","$score"],
sortBy: {"score": -1},
n: 3
}
}
}
}
] )
在这个例子中:
PlayerA
的得分是整数1
PlayerB
的得分是字符串"2"
PlayerC
的得分是空字符串""
因为排序指定为逆序{ "score" : -1 }
,字符串的字面量排在PlayerA
的数值得分之前:
js
[
{
_id: "G1",
playerId: [ [ "PlayerB", "2" ], [ "PlayerC", "" ], [ "PlayerA", 1 ] ]
}
]
举例
使用下面的命令创建gamescores
集合:
js
db.gamescores.insertMany([
{ playerId: "PlayerA", gameId: "G1", score: 31 },
{ playerId: "PlayerB", gameId: "G1", score: 33 },
{ playerId: "PlayerC", gameId: "G1", score: 99 },
{ playerId: "PlayerD", gameId: "G1", score: 1 },
{ playerId: "PlayerA", gameId: "G2", score: 10 },
{ playerId: "PlayerB", gameId: "G2", score: 14 },
{ playerId: "PlayerC", gameId: "G2", score: 66 },
{ playerId: "PlayerD", gameId: "G2", score: 80 }
])
查找三个得分最高的
使用$topN
查找单个游戏中得分最高的3个:
js
db.gamescores.aggregate( [
{
$match : { gameId : "G1" }
},
{
$group:
{
_id: "$gameId",
playerId:
{
$topN:
{
output: ["$playerId", "$score"],
sortBy: { "score": -1 },
n:3
}
}
}
}
] )
本例中:
- 使用
$match
阶段用一个gameId
对结果进行筛选,即:G1
- 使用
$group
阶段依据gameId
对结果进行分组,本例中只有一个分组G1
- 使用
sortBy: { "score": -1 }
按照得分进行逆序排序 - 使用
output : ["$playerId"," $score"]
为$topN
指定输出字段 - 使用
$topN
返回游戏得分最高的3个选手和得分
结果如下:
js
[
{
_id: 'G1',
playerId: [ [ 'PlayerC', 99 ], [ 'PlayerB', 33 ], [ 'PlayerA', 31 ] ]
}
]
与下面的SQL查询等价:
sql
SELECT T3.GAMEID,T3.PLAYERID,T3.SCORE
FROM GAMESCORES AS GS
JOIN (SELECT TOP 3
GAMEID,PLAYERID,SCORE
FROM GAMESCORES
WHERE GAMEID = 'G1'
ORDER BY SCORE DESC) AS T3
ON GS.GAMEID = T3.GAMEID
GROUP BY T3.GAMEID,T3.PLAYERID,T3.SCORE
ORDER BY T3.SCORE DESC
查找全部游戏中三个最高的得分
使用$topN
查找所有游戏中得分最高的三个
js
db.gamescores.aggregate( [
{
$group:
{ _id: "$gameId", playerId:
{
$topN:
{
output: [ "$playerId","$score" ],
sortBy: { "score": -1 },
n: 3
}
}
}
}
] )
在本例中:
- 使用
$group
按照groupId
对结果排序 - 使用
output : ["$playerId", "$score"]
指定bottom
输出的字段 - 使用
sortBy: { "score": -1 }
按照得分进行逆序排序 - 使用
$topN
返回所有游戏中得分最高的三个
结果如下:
js
[
{
_id: 'G1',
playerId: [ [ 'PlayerC', 99 ], [ 'PlayerB', 33 ], [ 'PlayerA', 31 ] ]
},
{
_id: 'G2',
playerId: [ [ 'PlayerD', 80 ], [ 'PlayerC', 66 ], [ 'PlayerB', 14 ] ]
}
]
这个操作与下面的SQL语句等价:
sql
SELECT PLAYERID,GAMEID,SCORE
FROM(
SELECT ROW_NUMBER() OVER (PARTITION BY GAMEID ORDER BY SCORE DESC) AS GAMERANK,
GAMEID,PLAYERID,SCORE
FROM GAMESCORES
) AS T
WHERE GAMERANK <= 3
ORDER BY GAMEID
基于分组key来计算参数n
可以动态指定n
的值,在本例中$cond
表达式用在gameId
字段:
js
db.gamescores.aggregate([
{
$group:
{
_id: {"gameId": "$gameId"},
gamescores:
{
$topN:
{
output: "$score",
n: { $cond: { if: {$eq: ["$gameId","G2"] }, then: 1, else: 3 } },
sortBy: { "score": -1 }
}
}
}
}
] )
在本例中:
- 使用
$group
按照groupId
对结果排序 - 使用
output : "$score"
指定$topN
输出的字段 - 如果
gameId
是G2
则n
为1,否则n
为3 - 使用
sortBy: { "score": -1 }
按照得分进行逆序排序
操作结果如下:
js
[
{ _id: { gameId: 'G1' }, gamescores: [ 99, 33, 31 ] },
{ _id: { gameId: 'G2' }, gamescores: [ 80 ] }
]