在Mongo中更新嵌套数组

时间:2015-04-20 04:53:39

标签: mongodb mongodb-query

我在更新嵌套数组时遇到了很多问题,我创建了一个非常简单的测试,它似乎没有用,有没有人遇到过这个问题:

数据:

 [ { "A": { "B": [ { "C": [ 1, 2, 3 ] }, { "C": [ 1, 2, 3 ] } ] } }, { "A": { "B": [ { "C": [ 1, 3 ] }, { "C": [ 1, 3 ] } ] } } ]

发现:

db.arrayQuery.find({"A.B.C": { $in: [1] }})

然后更新:

db.arrayQuery.update({"A.B.C": { $in: [1] }},{$pull : { "A.B.C" : 1}},{multi: true})

我得到cannot use the part (B of A.B.C) to traverse the element 我在这里读了一些问题,建议我只使用{$pull : { "C" : 1}},我不再收到错误,但没有任何反应。

2 个答案:

答案 0 :(得分:1)

Mongo $elemMatch用于此案例,查询如下

db.arrayQuery.update({"A.B":{"$elemMatch":{"C":{"$in":[1]}}}},{"$pull":{"A.B.$.C":{"$in":[1]}}},false,true)

答案 1 :(得分:0)

MongoDB不支持匹配到数组的多个级别,因为位置运算符仅支持一级深度而只支持第一个匹配元素。有一个 JIRA 票证。有关其他类似问题,请参阅 Multiple use of the positional $ operator to update nested arrays

你有几个选择;考虑通过展平结构来修改你的模式,这样每个文档只代表一个数组级别,即:

A : {
    B: {
        C: [...]
    }
}

另一个选项是使用 MapReduce 操作生成一个集合,该集合的文档属性具有每个对应arrayQuery文档的数组索引位置值。 MapReduce的基本思想是它使用JavaScript作为其查询语言,但这往往比聚合框架慢得多,不应该用于实时数据分析。

在MapReduce操作中,您需要定义几个步骤,即映射步骤(将每个arrayQuery B数组映射到集合中的每个文档,并且操作可以不执行任何操作或发出一些对象密钥和投影值)和减少步骤(采用发射值列表并将其减少为单个元素)。

对于地图步骤,理想情况下,您希望获取集合中的每个文档,每个B数组字段的索引以及包含$pull键的另一个键。

您的简化步骤将是一个函数(无效),简单定义为var reduce = function() {};

MapReduce操作的最后一步将创建一个名为arrayUpdates的单独集合,其中包含已发出的操作数组对象以及具有$pull条件的字段。在原始集合上运行MapReduce操作时,可以定期更新此集合。 总而言之,这个MapReduce方法看起来像:

var map = function(){
    for(var i = 0; i < this.A.B.length; i++){
        emit( 
            {
                "_id": this._id, 
                "index": i 
            }, 
            {
                "index": i, 
                A: {
                   B: this.A.B[i]
                },            
                "update": {
                    "value": "A.B." + i.toString() + ".C" // this projects the $pull query with the dynamic array element positions
                }                    
            }
        );
    }
};

var reduce = function(){};

db.arrayQuery.mapReduce(
    map,
    reduce,
    {
        "out": {
            "replace": "arrayUpdates"
        }
    }
);

从MapReduce操作中查询输出集operations通常会给你结果:

db.arrayUpdates.findOne()

<强>输出

/* 1 */
{
    "_id" : {
        "_id" : ObjectId("5534da99180e849972938fe8"),
        "index" : 0
    },
    "value" : {
        "index" : 0,
        "A" : {
            "B" : {
                "C" : [ 
                    1, 
                    2, 
                    3
                ]
            }
        },
        "update" : {
            "value" : "A.B.0.C"
        }
    }
}

然后,您可以使用db.arrayUpdates.find()方法中的光标迭代并相应地更新您的集合:

var cur = db.arrayUpdates.find({"value.A.B.C": 1 });

// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
    var doc = cur.next();
    var update = { "$pull": {} };
    // set the update query object
    update["$pull"][doc.value.update.value] = 1;

    db.arrayQuery.update({ "A.B.C": 1 }, update );
};

这会使用查询

为每个文档提取C数组中的值1
db.arrayQuery.update({ "A.B.C": 1 }, update ); 

例如,第一个匹配文档的对象update可以是{ $pull: {"A.B.1.C": 1} }

因此,运行上述内容后,db.arrayQuery.find()查询的最终结果将是:

/* 0 */
{
    "_id" : ObjectId("5534da99180e849972938fe8"),
    "A" : {
        "B" : [ 
            {
                "C" : [ 
                    2, 
                    3
                ]
            }, 
            {
                "C" : [ 
                    2, 
                    3
                ]
            }
        ]
    }
}

/* 1 */
{
    "_id" : ObjectId("5534da99180e849972938fe9"),
    "A" : {
        "B" : [ 
            {
                "C" : [ 
                    3
                ]
            }, 
            {
                "C" : [ 
                    3
                ]
            }
        ]
    }
}