当前位置: 首页 > 知识库问答 >
问题:

组内MongoDB聚合排序和限制[重复]

蒙才
2023-03-14

我有一个具有以下模式的待售项目集合:

var itemSchema = new Schema({
    "category" : { type : Schema.Types.ObjectId, ref : 'Category' },
    "merchant" : { type : Schema.Types.ObjectId, ref : 'Merchant' },
    "rating" : Number
})

我继承了一个聚合查询,它返回匹配类别的项目,按商家分组,组按组中的最大评级排序:

Item.aggregate([
      { "$match" : { category : categoryId, merchant : { $ne : null }}},
      { "$group" : { _id : "$merchant", 
                    rating : { $max : "$rating" }, 
                    items : { $push : "$$ROOT" }}},
      { "$sort" : { rating : -1 }}
    ], { allowDiskUse : true })
    .skip(skip)
    .limit(limit)

在此之后,代码继续按评级对每组中的项目进行排序,并删除除每组中排名前2位之外的所有项目。

作为聚合函数的一部分,是否可以在组内执行此排序和限制,以便聚合只返回每组中评级最高的两个项目?

共有2个答案

巫马瀚漠
2023-03-14

我所做的是只推送id,然后添加切片。

$group: {
    _id: "$merchant",
    items : { $push : "$_id" }
},
// ...

$project: {
  items: {$slice: ["$items", offset, limit]}
}

在查询之后,我填充结果:

return Item.populate(result, {path: 'items'})
仇建茗
2023-03-14

在可预见的不久的将来,在当前的聚合框架中尝试这样做并不是最明智的想法。主要问题当然来自您已经拥有的代码中的这一行:

"items" : { "$push": "$$ROOT" }

这就意味着,基本上需要发生的是,分组键中的所有对象都需要被放入一个数组中,以便在以后的代码中得到“top N”结果。

这显然无法扩展,因为该阵列本身的大小可能会超过16MB的BSON限制,并且无法覆盖分组文档中的其余数据。这里的主要问题是,不可能“限制推送”仅限于一定数量的物品。在这件事上,吉拉的问题由来已久。

仅出于这个原因,最实用的方法就是为每个分组键的“前N个”项运行单独的查询。这些甚至不需要是。aggregate()stations(取决于数据),实际上可以是任何只限制您想要的“前N个”值的东西。

您的体系结构似乎位于节点上。js和mongoose,但是任何支持异步IO和并行执行查询的东西都是最好的选择。理想情况下,它有自己的API库,支持将这些查询的结果组合到单个响应中。

例如,有一个使用您的架构和可用库(特别是async)的简化示例列表,它完全执行并行和组合结果:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      async.waterfall(
        [
          function(callback) {
            Test.distinct("merchant",callback);
          },
          function(merchants,callback) {
            async.concat(
              merchants,
              function(merchant,callback) {
                Test.find({ "merchant": merchant })
                  .sort({ "rating": -1 })
                  .limit(2)
                  .exec(callback);
              },
              function(err,results) {
                console.log(JSON.stringify(results,undefined,2));
                callback(err);
              }
            );
          }
        ],
        callback
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

这会导致输出中每个商家的前2个结果:

[
  {
    "_id": "560d153669fab495071553ce",
    "merchant": 1,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553cd",
    "merchant": 1,
    "rating": 2,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d1",
    "merchant": 2,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d0",
    "merchant": 2,
    "rating": 2,
    "__v": 0
  }
]

这确实是处理此问题的最有效方法,尽管它会占用资源,因为它仍然是多个查询。但是如果您尝试将所有文档存储在数组中并对其进行处理,则与聚合管道中消耗的资源相去甚远。

因此,考虑到文件数量不会导致BSON限制的违反,可以这样做。MongoDB当前版本中的方法对此并不适用,但即将发布的版本(截至编写之时,3.1.8 dev branch会这样做)至少在聚合管道中引入了一个$slice操作符。因此,如果您在聚合操作方面更聪明,并且首先使用排序$,那么就可以轻松地挑出数组中已排序的项目:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$project": {
            "items": { "$slice": [ "$items", 2 ] }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

这产生的基本结果与前2个项目在首先排序后从数组中“切片”相同。

在当前版本中,这实际上也是“可能的”,但有相同的基本限制,即这仍然需要在对内容进行排序后将所有内容推送到一个数组中。它只需要一种“迭代”的方法。您可以将其编码出来,为更多的条目生成聚合管道,但仅显示“两个”应该表明尝试以下操作并不是一个好主意:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$unwind": "$items" },
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$items" },
            "items": { "$push": "$items" }
          }},
          { "$unwind": "$items" },
          { "$redact": {
            "$cond": [
              { "$eq": [ "$items", "$first" ] },
              "$$PRUNE",
              "$$KEEP"
            ]
          }},
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$first" },
            "second": { "$first": "$items" }
          }},
          { "$project": {
            "items": {
              "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                  "$cond": [
                    { "$eq": [ "$$el", "A" ] },
                    "$first",
                    "$second"
                  ]
                }
              }
            }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

尽管在早期版本中“可能”(这是使用2.6引入的功能来缩短,因为您已经标记了根目录),基本步骤是存储数组,然后使用$first将每个项目“从堆栈中取出”,并将其(以及可能的其他项目)与数组中的项目进行比较以删除它们,然后从堆栈中取出“下一个第一个”项目,直到最终完成“前N个”。

直到有一天有这样一个操作允许$ush聚合累加器中的项目被限制在某个计数,那么这对于聚合来说并不是一个真正的实际操作。

如果您在这些结果中拥有的数据足够小,您可以做到这一点,如果数据库服务器具有足够的规格以提供真正的优势,它甚至可能比客户端处理更有效。但在大多数合理使用的实际应用程序中,这两种情况都有可能。

最好的选择是使用首先演示的“并行查询”选项。它总是能够很好地扩展,并且不需要“编写”这样的逻辑,即特定分组可能不会返回至少所需的总“前N”项,并且在它只执行每个查询并组合结果时,确定如何保留它们(省略了更长的示例)。

使用并行查询。它将比您现有的编码方法更好,而且它将在很大程度上优于聚合方法。至少在有更好的选择之前。

 类似资料:
  • 我如何在mongoDB中按对每个组进行排序和限制。 考虑以下数据: 现在,我将按国家分组,按评级排序,并将每组的数据限制为2。 所以答案是: 我想只使用聚合框架来实现这一点。 我试图包括排序的评级,但简单的查询后处理没有结果。

  • 例如,我有一个收藏: 如何在MongoDB中进行查询以按分组,然后按排序并以进行。我想得到这样的:

  • 给java类一些东西 我有一张物品清单 我希望能够对它们进行排序,这样它们就可以按照每个父对象的虚数的累积和排序,然后再按照虚数排序。 所以我最终 我知道用parentKey和sum of noThings映射它是 我想,也许包装我的Something类并获得每个父项的总密钥可能会在某种程度上起作用。 但看起来工作量很大,不太优雅。 如有任何意见/想法,将不胜感激。

  • 本文向大家介绍在MongoDB中执行聚合排序?,包括了在MongoDB中执行聚合排序?的使用技巧和注意事项,需要的朋友参考一下 您可以将method和$sort()运算符一起使用。为了理解这个概念,让我们用文档创建一个集合。使用文档创建集合的查询如下- 在method的帮助下显示集合中的所有文档。查询如下- 以下是输出- 这是对MongoDB聚合排序的查询。 情况1-每当您希望结果按降序排列时。查

  • 我有2周的时间来学习和使用MongoDB,我正在使用DataGridview构建一个简单的WinForm APP。 一切正常,但我添加了超过 1.000.000 个文档,现在它向我显示此错误: MongoDB.Driver.MongoCommandException:“命令聚合失败:排序超出了 104857600 字节的内存限制,但未选择加入外部排序。正在中止操作。Pass allowDiskUs

  • 我在尝试使用MongooseJs在Mongodb中按嵌套数组排序时遇到了一个小问题。 a)一个产品包含任务,每个任务都有子任务。 b)任务有顺序 这是一个示例产品文档: 结果: } 我正在使用MongoDB聚合管道来订购任务 结果: } 预期结果: 我真的很接近了,所有的排序似乎都在工作。我只需要一些帮助来将子任务放回父母体内。非常感谢任何帮助。 谢谢