MongoDB聚合运算符:$bottomN

$bottomN聚合运算符返回分组中指定顺序的最后n个元素,如果分组中的元素数量小于n,则返回分组的全部元素。从MongoDB5.2开始支持。

语法

js 复制代码
{
   $bottomN:
      {
         n: <expression>,
         sortBy: { <field1>: <sort order>, <field2>: <sort order> ... },
         output: <expression>
      }
}
  • n用于限制每组结果的数量,必须是正整数表达式,要么是常数,要么取决于$group_id
  • sortBy制定返回结果的顺序,语法类似于$sort
  • output指定分组元素输出的内容,可以是任何合法的表达式。

用法

  • $bottom不支持作为聚合表达式。
  • $bottom只支持作为window 操作符
  • 聚合管道调用$bottom受100M的限制,如果单组超过这一限制将报错。

关于null和缺失值的处理

  • $bottom不会过滤掉空值
  • $bottom会将缺失值转换为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:
               {
                  $bottomN:
                     {
                        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: [ [ "PlayerA", 1 ], [ "PlayerD", null ], [ "PlayerE", null ] ]
   }
]

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: {
               $bottomN:
               {
                  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 }
])

查找三个得分最低的

使用$bottomN查找单个游戏中得分最低的3个:

js 复制代码
db.gamescores.aggregate( [
   {
      $match : { gameId : "G1" }
   },
   {
      $group:
         {
            _id: "$gameId",
            playerId:
               {
                  $bottomN:
                  {
                     output: ["$playerId", "$score"],
                     sortBy: { "score": -1 },
                     n:3
                  }
               }
         }
   }
] )

本例中:

  • 使用$match阶段用一个gameId对结果进行筛选,即:G1
  • 使用$group阶段依据gameId对结果进行分组,本例中只有一个分组G1
  • 使用output : ["$playerId"," $score"]bottom指定输出字段
  • 使用sortBy: { "score": -1 }按照得分进行逆序排序
  • 使用$bottomN返回游戏得分最低的3个选手和得分

结果如下:

js 复制代码
[
   {
      _id: "G1",
      playerId: [ [ "PlayerB", 33 ], [ "PlayerA", 31 ], [ "PlayerD", 1 ] ]
   }
]

与下面的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) AS T3
            ON GS.GAMEID = T3.GAMEID
GROUP BY T3.GAMEID,T3.PLAYERID,T3.SCORE
   ORDER BY T3.SCORE DESC

查找全部游戏中三个最低的得分

使用$bottomN查找所有游戏中得分最低的三个

js 复制代码
db.gamescores.aggregate( [
      {
         $group:
         { _id: "$gameId", playerId:
            {
               $bottomN:
                  {
                     output: [ "$playerId","$score" ],
                     sortBy: { "score": -1 },
                     n: 3
                  }
            }
         }
      }
] )

在本例中:

  • 使用$group按照groupId对结果排序
  • 使用output : ["$playerId", "$score"]指定bottom输出的字段
  • 使用sortBy: { "score": -1 }按照得分进行逆序排序
  • 使用$bottomN返回所有游戏中得分最低的三个

结果如下:

js 复制代码
[
   {
      _id: "G1",
      playerId: [ [ "PlayerB", 33 ], [ "PlayerA", 31 ], [ "PlayerD", 1 ] ]
   },
   {
      _id: "G2",
      playerId: [ [ "PlayerC", 66 ], [ "PlayerB", 14 ], [ "PlayerA", 10 ] ]
   }
]

这个操作与下面的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 >= 2
ORDER BY GAMEID

基于分组key来计算参数n

可以动态指定n的值,在本例中$cond表达式用在gameId字段:

js 复制代码
db.gamescores.aggregate([
   {
      $group:
      {
         _id: {"gameId": "$gameId"},
         gamescores:
            {
               $bottomN:
                  {
                     output: "$score",
                     n: { $cond: { if: {$eq: ["$gameId","G2"] }, then: 1, else: 3 } },
                     sortBy: { "score": -1 }
                  }
            }
      }
   }
] )

在本例中:

  • 使用$group按照groupId对结果排序
  • 使用output : "$score"指定$bottomN输出的字段
  • 如果gameIdG2n为1,否则n为3
  • 使用sortBy: { "score": -1 }按照得分进行逆序排序

操作结果如下:

js 复制代码
[
   { _id: { gameId: "G2" }, gamescores: [ 10 ] },
   { _id: { gameId: "G1" }, gamescores: [ 33, 31, 1 ] }
]
相关推荐
扶尔魔ocy11 分钟前
【Linux C/C++开发】轻量级关系型数据库SQLite开发(包含性能测试代码)
linux·数据库·c++·sqlite
旋风菠萝27 分钟前
项目复习(1)
java·数据库·八股·八股文·复习·项目、
w236173460129 分钟前
Django框架漏洞深度剖析:从漏洞原理到企业级防御实战指南——为什么你的Django项目总被黑客盯上?
数据库·django·sqlite
2302_809798321 小时前
【JavaWeb】MySQL
数据库·mysql
drowingcoder1 小时前
MySQL相关
数据库
Musennn2 小时前
MySQL刷题相关简单语法集合
数据库·mysql
Think Spatial 空间思维3 小时前
【HTTPS基础概念与原理】TLS握手过程详解
数据库·网络协议·https
laowangpython3 小时前
MySQL基础面试通关秘籍(附高频考点解析)
数据库·mysql·其他·面试
mooyuan天天3 小时前
SQL注入报错“Illegal mix of collations for operation ‘UNION‘”解决办法
数据库·web安全·sql注入·dvwa靶场·sql报错
运维-大白同学4 小时前
go-数据库基本操作
开发语言·数据库·golang