MongoDB聚合运算符:$topN
文章目录
- MongoDB聚合运算符:$topN
- 语法
- 用法
- 关于null和缺失值的处理
- BSON数据类型排序
 
- 举例
- 查找三个得分最高的
- 查找全部游戏中三个最高的得分
- 基于分组key来计算参数n
 
 
$topN聚合运算符返回分组中指定顺序的最前面 
n个元素,如果分组中的元素数量小于 
n,则返回分组的全部元素。从MongoDB5.2开始支持。 
 
语法
{$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
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数组
如下:
[{_id: 'G1',playerId: [ [ 'PlayerD', null ], [ 'PlayerE', null ], [ 'PlayerA', 1 ] ]}
]
BSON数据类型排序
当不同类型排序是,使用BSON数据类型的顺序进行排序:
- 当进行正序排序时(由小到大),字符串的优先级在数值之前
- 当进行逆序排序时(由大到小),字符串的优先级在数值之前
下面的例子中包含了字符串和数值类型:
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的数值得分之前:
[{_id: "G1",playerId: [ [ "PlayerB", "2" ], [ "PlayerC", "" ], [ "PlayerA", 1 ] ]}
]
举例
使用下面的命令创建gamescores集合:
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个:
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个选手和得分
结果如下:
[{_id: 'G1',playerId: [ [ 'PlayerC', 99 ], [ 'PlayerB', 33 ], [ 'PlayerA', 31 ] ]}
]
与下面的SQL查询等价:
SELECT T3.GAMEID,T3.PLAYERID,T3.SCORE
FROM GAMESCORES AS GS
JOIN (SELECT TOP 3GAMEID,PLAYERID,SCOREFROM GAMESCORESWHERE GAMEID = 'G1'ORDER BY SCORE DESC) AS T3ON GS.GAMEID = T3.GAMEID
GROUP BY T3.GAMEID,T3.PLAYERID,T3.SCOREORDER BY T3.SCORE DESC
查找全部游戏中三个最高的得分
使用$topN查找所有游戏中得分最高的三个
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返回所有游戏中得分最高的三个
结果如下:
[{_id: 'G1',playerId: [ [ 'PlayerC', 99 ], [ 'PlayerB', 33 ], [ 'PlayerA', 31 ] ]},{_id: 'G2',playerId: [ [ 'PlayerD', 80 ], [ 'PlayerC', 66 ], [ 'PlayerB', 14 ] ]}
]
这个操作与下面的SQL语句等价:
SELECT PLAYERID,GAMEID,SCORE
FROM(SELECT ROW_NUMBER() OVER (PARTITION BY GAMEID ORDER BY SCORE DESC) AS GAMERANK,GAMEID,PLAYERID,SCOREFROM GAMESCORES
) AS T
WHERE GAMERANK <= 3
ORDER BY GAMEID
基于分组key来计算参数n
可以动态指定n的值,在本例中$cond表达式用在gameId字段:
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 }按照得分进行逆序排序
操作结果如下:
[{ _id: { gameId: 'G1' }, gamescores: [ 99, 33, 31 ] },{ _id: { gameId: 'G2' }, gamescores: [ 80 ] }
]