【问题标题】:MongoDB: $pull / $unset with multiple conditionsMongoDB:具有多个条件的 $pull / $unset
【发布时间】:2015-09-14 05:41:57
【问题描述】:

示例文档:

{
  _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

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

如果有人对此有可行的解决方案,那就太好了。

【问题讨论】:

    标签: arrays mongodb mongodb-query


    【解决方案1】:

    不确定我是否完全理解了这一点,但是要“批量”更新文档,除了原始 $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”的所有内容以及从“设置”长度的差异中检测到“重复”的所有重复项。

    如果您只想将 5 的值作为重复项删除,您可以采用类似的逻辑方法进行检测和修改,而不是使用“集合运算符”删除任何“重复项”使其成为有效的“集合” ”。

    无论如何,某些检测策略会比迭代更新直到“除了一个”值消失为止更好。


    当然你可以稍微简化你的语句并删除一个更新操作,这并不漂亮,因为$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 之后的“第一个”参数。

    【讨论】:

    • 对不起。这与删除重复项无关。它实际上只是“删除低于 4 的所有内容和一个(或零)等于 5 的条目”。
    • @BenjaminM 请准确定义“一或零”。你的意思是如果5 有多个值,就像[4,5,5,5] 一样,那么你希望结果是[4,5,5]?还是别的什么?
    • 如果条件是$lt: 4$eq: 5,那么这里有一些例子:[] -> [][2] -> [][4] -> [4][5] -> [][1,1,2,2,3,4,4] -> [4,4][1,4,5,5,5] -> [4,5,5][3,5] -> []。换句话说:“每个低于4的条目和一个等于5的条目(如果有任何条目等于5)”
    • @BenjaminM 知道了。如前所述,如果$or 会在$pull 中被接受,那么这里的“好”事情将是。但事实并非如此,因此需要即兴发挥。
    • 遗憾的是,您使用$in 的查询不起作用,因为在我的现实世界中,这些数字是ISODates,我需要$lt 才能工作:(
    【解决方案2】:

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

    filter() 方法创建一个带有foo.bar$lt: 4 的新闻数组,然后您使用splice 方法删除这些值,并且第一个值等于5foo.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);
    } )
    

    【讨论】:

    • 但这不是一个安全的操作,如果同时有一些插入到这个数组中。
    • 所以记录在案,我认为@BenjaminM 会同意这是关于“服务器”处理的。您总是可以在客户端代码中进行精简,但这并不是很有效。更新不应该需要循环和“测试”集合中的每个文档才能生效。并同意“不安全”。 .save() 可以“覆盖”。
    猜你喜欢
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    • 2020-09-23
    • 2022-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多