MongoDB:$ pull / $ unset with multiple conditions

时间:2015-06-27 09:35:52

标签: arrays mongodb mongodb-query

示例文档:

{
  _id: 42,
  foo: {
    bar: [1, 2, 3, 3, 4, 5, 5]
  }
}

查询:

我想“删除foo.bar$lt: 4的所有条目以及匹配$eq: 5的第一个匹配条目。 重要提示$eq部分只能删除单个条目!

我有一个使用3个更新查询的工作解决方案,但这对于这个简单的任务来说太过分了。不过,这就是我到目前为止所做的:

1。查找匹配$eq: 5$unset的第一个条目。 (如您所知:$unset不删除它。它只是将其设置为null):

update(
  { 'foo.bar': 5 },
  { $unset: { 'foo.bar.$': 1 } } 
)

2。 $pull所有条目$eq: null,以便之前的5真的消失了:

update(
  {},
  { $pull: { 'foo.bar': null } } 
)

3。 $pull所有条目$lt: 4

update(
  {},
  { $pull: { 'foo.bar': { $lt: 4 } } } 
)

结果文件:

{
  _id: 42,
  foo: {
    bar: [4, 5]
  }
}

想法和想法:

  • 扩展查询 1。,以便$unset条目$lt: 4和一个条目$eq: 5。之后我们可以执行查询 2。,无需查询 3。

  • 将查询 2。扩展到$pull$or: [{$lt: 4}, {$eq: 5}]匹配的所有内容。然后就不需要查询 3。

  • 将查询 2。扩展为$pull $not: { $gte: 4 }的所有内容。此表达式应与$lt: 4 $eq: null匹配。

我已经尝试过实现这些查询,但有时会抱怨查询语法,有时候查询确实执行了,只是删除了什么。

如果有人为此提供了有效的解决方案,那就太好了。

2 个答案:

答案 0 :(得分:1)

不确定我是否明白了这一点,但是为了“批量”更新文档,除了oringal $pull之外,你总是可以采用这种方法,并添加一些“检测”你需要删除的文件“复制“5来自:

// Remove less than four first
db.collection.update({},{ "$pull": { "foo.bar": { "$lt": 4 } } },{ "multi": true });

// Initialize Bulk
var bulk = db.collection.initializeOrderdBulkOp(),
    count = 0;

// Detect and cycle documents with duplicate five to be removed
db.collection.aggregate([
    // Project a "reduced" array and calculate if the same size as orig
    { "$project": { 
         "foo.bar": { "$setUnion": [ "$foo.bar", [] ] },
         "same": { "$eq": [
             { "$size": {  "$setUnion": [ "$foo.bar", [] ] } },
             { "$size": "$foo.bar" }
         ] }
    }},
    // Filter the results that were unchanged
    { "$match": { "same": true } }
]).forEach(function(doc) {
    bulk.find({ "_id": doc._id })
        .updateOne({ "$set": { "foo.bar": doc.foo.bar.sort() } });
    count++;

    // Execute per 1000 processed and re-init
    if ( count % 1000 == 0 ) {
        bulk.execute();
        bulk = db.collection.initializeOrderdBulkOp();
    }
});

// Clean up any batched
if ( count % 1000 != 0 )
    bulk.execute();

删除小于“4”的任何内容以及从“set”长度的差异中检测到“重复”的所有重复项。

如果您只想将5的值删除为重复项,则可以对检测和修改采用类似的逻辑方法,而不是使用“set operators”删除任何“重复”的内容使其成为有效的“设置”。

无论如何,一些检测策略将比迭代更新更好,直到“除了一个”之外的值都消失了。

当然你可以稍微简化你的语句并删除一个更新操作,因为$pull在查询中不允许$or条件,所以它并不漂亮,但是我希望你能得到这个想法适用:

db.collection.update(
    { "foo.bar": 5 },
    { "$unset": { "foo.bar.$": 1 } },
    { "multi": true }
); // same approach

// So include all the values "less than four"
db.collection.update(
    { "foo.bar": { "$in": [1,2,3,null] } },
    { "$pull": { "foo.bar": { "$in": [1,2,3,null] } }},
    { "multi": true }
);

处理稍微少一些但当然需要精确的整数值。否则坚持你正在做的三个更新。比在代码中循环更好。

作为参考,遗憾的是,无效的“更好”语法将是这样的:

db.collection.update(
    { 
        "$or": [
            { "foo.bar": { "$lt": 4 } },
            { "foo.bar": null }
        ]
    },
    { 
        "$pull": { 
            "$or": [
                { "foo.bar": { "$lt": 4 } },
                { "foo.bar": null }
            ]
        }
    },
    { "multi": true }
);

可能值得一个JIRA问题,但我怀疑主要是因为数组元素不是紧跟在$pull之后的“第一个”参数。

答案 1 :(得分:1)

您可以使用Array.prototype.filter()Array.prototype.splice()方法

filter()方法会创建一个foo.bar$lt: 4的新闻数组,然后使用splice方法删除这些值,第一个值等于5来自foo.bar

var idx = [];
db.collection.find().forEach(function(doc){ 
    idx = doc.foo.bar.filter(function(el){  
        return el < 4;
    }); 
    for(var i in idx){   
        doc.foo.bar.splice(doc.foo.bar.indexOf(idx[i]), 1); 
    } 
    doc.foo.bar.splice(doc.foo.bar.indexOf(5), 1); 
    db.collection.save(doc);
} )