使用 MongoDB 进行数据分区

Ash*_*shh 4 mongoose mongodb node.js 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
  }
]
Run Code Online (Sandbox Code Playgroud)

现在我想group通过setting并且只想要结果中最靠前的两个数据useless

所以我的输出应该是sort通过计数

[
  {
    "setting": "Contrast",
    "counting": 2
  },
  {
    "setting": "Sharpness",
    "counting": 2
  },
  {
    "setting": "Useless",
    "counting": 3
  }
]
Run Code Online (Sandbox Code Playgroud)

Nei*_*unn 5

如果你可以侥幸逃脱,那么最好将简化的结果“填充”到一个文档中,然后$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" } }
])
Run Code Online (Sandbox Code Playgroud)

如果它可能是一个非常“大”的结果甚至减少了,那么对“其余”$limit使用 a $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" } }
])
Run Code Online (Sandbox Code Playgroud)

甚至$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" } }
])
Run Code Online (Sandbox Code Playgroud)

几乎完全相同,并且都返回相同的结果:

{ "_id" : "Contrast", "counting" : 2 }
{ "_id" : "Sharpness", "counting" : 2 }
{ "_id" : "Useless", "counting" : 3 }
Run Code Online (Sandbox Code Playgroud)

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


如果您的 MongoDB 早于 3.4 并且结果"Useless"余数实际上太大而无法使用第一种方法的任何变体,那么简单的Promise解决方案基本上就是答案,一个用于aggregate基本计数,另一个用于基本计数,只需进行数学计算:

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)
  }
];
Run Code Online (Sandbox Code Playgroud)

或者没有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 */ )
Run Code Online (Sandbox Code Playgroud)

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

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

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