node.js - 使用 MongoDB 进行数据分区

标签 node.js mongodb mongoose mongodb-query aggregation-framework

我有以下收藏

[
  {
    "setting": "Volume",
    "_id": ObjectId("5a934e000102030405000000"),
    "counting": 1
  },
  {
    "setting": "Brightness",
    "_id": ObjectId("5a934e000102030405000001"),
    "counting": 1
  },
  {
    "setting": "Contrast",
    "_id": ObjectId("5a934e000102030405000002"),
    "counting": 1
  },
  {
    "setting": "Contrast",
    "_id": ObjectId("5a934e000102030405000003"),
    "counting": 1
  },
  {
    "setting": "Contrast",
    "_id": ObjectId("5a934e000102030405000004"),
    "counting": 0
  },
  {
    "setting": "Sharpness",
    "_id": ObjectId("5a934e000102030405000005"),
    "counting": 1
  },
  {
    "setting": "Sharpness",
    "_id": ObjectId("5a934e000102030405000006"),
    "counting": 1
  },
  {
    "setting": "Language",
    "_id": ObjectId("5a934e000102030405000007"),
    "counting": 1
  },
  {
    "setting": "Language",
    "_id": ObjectId("5a934e000102030405000008"),
    "counting": 0
  }
]

现在我想通过设置分组并且只希望结果中最上面的两个数据留在无用

所以我的输出应该在sort之后

[
  {
    "setting": "Contrast",
    "counting": 2
  },
  {
    "setting": "Sharpness",
    "counting": 2
  },
  {
    "setting": "Useless",
    "counting": 3
  }
]

最佳答案

如果您能逃脱惩罚,那么最好将简化后的结果“填充”到单个文档中,然后 $slice前两名和$sum其余的:

Model.aggregate([
  { "$group": {
    "_id": "$setting",
    "counting": { "$sum": "$counting" }
  }},
  { "$sort": { "counting": -1 } },
  { "$group": {
    "_id": null,
    "data": { "$push": "$$ROOT" }
  }},
  { "$addFields": {
     "data": {
       "$let": {
         "vars": { "top": { "$slice": ["$data", 0, 2 ] } },
         "in": {
           "$concatArrays": [
             "$$top",
             { "$cond": {
               "if": { "$gt": [{ "$size": "$data" }, 2] },
               "then": 
                 [{ 
                   "_id": "Useless",
                   "counting": {
                     "$sum": {
                       "$map": {
                         "input": {
                           "$filter": {
                             "input": "$data",
                             "cond": { "$not": { "$in": [ "$$this._id", "$$top._id" ] } }
                           }
                         },
                         "in": "$$this.counting"
                       }
                     }
                   }
                 }],
               "else": []
             }}
           ]
         }
       }
     }
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": { "newRoot": "$data" } }
])

如果它可能是一个非常“大”的结果甚至减少,那么 $limit使用$facet对于“休息”:

Model.aggregate([
  { "$facet": {
    "top": [
      { "$group": {
        "_id": "$setting",
        "counting": { "$sum": "$counting" }
      }},
      { "$sort": { "counting": -1 } },
      { "$limit": 2 }
    ],
    "rest": [
      { "$group": {
        "_id": "$setting",
        "counting": { "$sum": "$counting" }
      }},
      { "$sort": { "counting": -1 } },
      { "$skip": 2 },
      { "$group": {
        "_id": "Useless",
        "counting": { "$sum": "$counting" }
      }}
    ]
  }},
  { "$project": {
    "data": {
      "$concatArrays": [
        "$top","$rest"
      ]
    }
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": { "newRoot": "$data" } }
])

甚至$lookup使用 MongoDB 3.6:

Model.aggregate([
  { "$group": {
    "_id": "$setting",
    "counting": { "$sum": "$counting" }
  }},
  { "$sort": { "counting": -1 } },
  { "$limit": 2 },
  { "$group": {
    "_id": null,
    "top": { "$push": "$$ROOT" }   
  }},
  { "$lookup": {
    "from": "colllection",
    "let": { "settings": "$top._id" },
    "pipeline": [
      { "$match": {
        "$expr": {
          "$not": { "$in": [ "$setting", "$$settings" ] }
        }
      }},
      { "$group": {
        "_id": "Useless",
        "counting": { "$sum": "$counting" }
      }}
    ],
    "as": "rest"
  }},
  { "$project": {
    "data": {
      "$concatArrays": [ "$top", "$rest" ]
    }
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": { "newRoot": "$data" } }
])

实际上几乎都一样,并且都返回相同的结果:

{ "_id" : "Contrast", "counting" : 2 }
{ "_id" : "Sharpness", "counting" : 2 }
{ "_id" : "Useless", "counting" : 3 }

可选 $project就在每个的末尾而不是 $replaceRoot如果控制字段名称对您来说真的很重要。通常我只是坚持使用 $group默认值


如果您的 MongoDB 早于 3.4 并且生成的 “无用” 余数实际上太大而无法使用第一种方法的任何变体,那么简单的 Promise 解决方案是基本上就是答案,一个用于聚合,另一个用于基本计数并简单地做数学运算:

let [docs, count] = await Promise.all([
  Model.aggregate([
    { "$group": {
      "_id": "$setting",
      "counting": { "$sum": "$counting" }
    }},
    { "$sort": { "counting": -1 } },
    { "$limit": 2 },
  ]),
  Model.count().exec()
]);

docs = [ 
  ...docs,
  { 
    "_id": "Useless",
    "counting": count - docs.reduce((o,e) => o + e.counting, 0)
  }
];

或者没有async/await:

Promise.all([
  Model.aggregate([
    { "$group": {
      "_id": "$setting",
      "counting": { "$sum": "$counting" }
    }},
    { "$sort": { "counting": -1 } },
    { "$limit": 2 },
  ]),
  Model.count().exec()
]).then(([docs, count]) => ([ 
  ...docs,
  { 
    "_id": "Useless",
    "counting": count - docs.reduce((o,e) => o + e.counting, 0)
  }
]).then( result => /* do something */ )

这基本上是古老的“总页数”方法的一种变体,只需运行单独的查询来计算集合项目。

运行单独的请求通常是执行此操作的古老方法,而且通常效果最好。其余的解决方案主要针对“聚合技巧”,因为这正是您所要求的,这就是您通过对同一事物展示不同变体而得到的答案。

一个变体将所有结果放入单个文档(如果可能,当然是由于 BSON 的限制),而其他变体基本上通过以不同的形式再次运行查询来改变“古老”的方法。 $facet 并行,$lookup 串行。

关于node.js - 使用 MongoDB 进行数据分区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50315765/

相关文章:

python - 如何用python启动mongodb?

javascript - 无法同步 Mongoose 操作以返回数组

javascript - 如何使用更好的 saveAsync 来扩展 Mongooose 原型(prototype)

mongodb - 如何将数据库从 mongolab.com 复制或导入到我的本地 mongodb 服务器?

javascript - MongoDB&JavaScript 堆内存不足

javascript - 如何使用 promise 来跳过读取拒绝访问的锁定文件?

node.js - NodeMailer - 使用 Google 服务帐户发送邮件失败,因为 "Username and Password not accepted"

session - Socket.io 和 session 数据

javascript - 断言在磁带中抛出 - Node

mongodb - 使用 Go 插入和查询 MongoDB 数据