根据字段数组大小对文档进行分组和删除文档

group by and delete documents based on a field array size

本文关键字:文档 删除 字段 数组      更新时间:2023-09-26

我有这样的文档:

{
    "_id" : ObjectId("53bcedc39c837bba3e1bf1c2"),
    id : "abc1",
    someArray: [ 1 , 10 , 11]
}
{
    "_id" : ObjectId("53bcedc39c837bba3e1bf1c4"),
    id : "abc1",
    someArray: [ 1 , 10]
}
... other similar documents with different Ids

我想浏览整个集合,并删除someArray最小的文档,按id分组。因此,在本例中,我按abc1进行分组(得到2个文档),然后第二个文档将是要删除的文档,因为它在someArray中的计数最少。

没有$count累加器,所以我不知道如何使用$group

此外,会有1000个Id有这样的重复,所以如果有批量检查/删除这样的事情会很好(可能是一个愚蠢的问题,对不起,Mongo对我来说都是新的!)

删除"重复项"在这里是一个过程,没有简单的方法可以将"识别"重复项和"删除"它们作为一个单独的语句。这里的另一个特殊之处是,查询表单"通常"不能确定数组的大小,当然也不能根据文档中尚未存在的数组进行排序。

所有案例基本上都归结为

  1. 识别"重复"的文档列表,然后理想情况下,用手指触摸要删除的特定文档,或者更多地触摸到你"不"想从可能的重复中删除的文档。

  2. 处理该列表以实际执行删除。

考虑到这一点,您希望有一个2.6或更高版本的现代MongoDB,在那里您可以从聚合方法中获得一个游标。您还希望批量操作API在这些版本中可用,以获得最佳速度:

var bulk = db.collection.initializeOrderedBulkOp();
var counter = 0;
db.collection.aggregate([
    { "$project": {
        "id": 1,
        "size": { "$size": "$someArray" }
    }},
    { "$sort": { "id": 1, "size": -1 } },
    { "$group": {
        "_id": "$id",
        "docId": { "$first": "$_id" }
    }}
]).forEach(function(doc) {
    bulk.find({ "id": doc._id, "_id": { "$ne": doc.docId }).remove();
    counter++;
    // Send to server once every 1000 statements only
    if ( counter % 1000 == 0 ) {
        bulk.execute();
        bulk = db.collection.initializeOrderedBulkOp();  // need to reset
    }
});
// Clean up results that did not round to 1000
if ( counter % 1000 != 0 )
    bulk.execute();

您仍然可以对旧版本的MongoDB执行相同的操作,但.aggregate()的结果必须低于16MB,这是BSON的限制。这仍然应该是很多,但对于旧版本,您也可以使用mapReduce输出到集合。

但对于一般的聚合响应,您会得到一个结果数组,而且您也没有其他方便的方法来查找数组的大小。所以还有一点工作:

var result = db.collection.aggregate([
    { "$unwind": "$someArray" },
    { "$group": {
        "_id": "$id",
        "id": { "$first": "$id" },
        "size": { "$sum": 1 }
    }},
    { "$sort": { "id": 1, "size": -1 } },
    { "$group": {
        "_id": "$id",
        "docId": { "$first": "$_id" }
    }}
]);
result.result.forEach(function(doc) {
    db.collection.remove({ "id": doc._id, "_id": { "$ne": doc.docId } });
});

因此,没有用于大型结果的游标,也没有批量操作,因此每一个"删除"都需要单独发送到服务器。

因此,在MongoDB中没有"子查询",甚至当有超过"两个重复项"时,也没有从其他重复项中挑出您不想删除的文档的方法。但这是一般的方法

需要注意的是,如果数组的"大小"对于"排序"等目的来说很重要,那么最好的做法是将该"大小"保留为文档的另一个属性,这样就可以更容易地进行这些操作,而无需像这里所做的那样"计算"。