Monta
Monta

Reputation: 1180

Mongodb Update/Upsert array exact match

I have a collection :

gStats : {
     "_id" : "id1",
     "criteria" : ["key1":"value1", "key2":"value2"],
     "groups" : [
             {"id":"XXXX", "visited":100, "liked":200},
             {"id":"YYYY", "visited":30, "liked":400}
     ]
}

I want to be able to update a document of the stats Array of a given array of criteria (exact match).

I try to do this on 2 steps :

Pull the stat document from the array of a given "id" :

db.gStats.update({
    "criteria" : {$size : 2},
    "criteria" : {$all : [{"key1" : "2096955"},{"value1" : "2015610"}]}
},
{
    $pull : {groups : {"id" : "XXXX"}}
}
)

Push the new document

db.gStats.findAndModify({
    query : {
        "criteria" : {$size : 2},
        "criteria" : {$all : [{"key1" : "2015610"}, {"key2" : "2096955"}]}
    },
    update : {
        $push : {groups : {"id" : "XXXX", "visited" : 29, "liked" : 144}}
    },
    upsert : true
})

The Pull query works perfect. The Push query gives an error :

2014-12-13T15:12:58.571+0100 findAndModifyFailed failed: {
        "value" : null,
        "errmsg" : "exception: Cannot create base during insert of update. Cause
d by :ConflictingUpdateOperators Cannot update 'criteria' and 'criteria' at the
same time",
        "code" : 12,
        "ok" : 0
} at src/mongo/shell/collection.js:614

Upvotes: 1

Views: 2715

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151082

Neither query is working in reality. You cannot use a key name like "criteria" more than once unless under an operator such and $and. You are also specifying different fields (i.e groups) and querying elements that do not exist in your sample document.

So hard to tell what you really want to do here. But the error is essentially caused by the first issue I mentioned, with a little something extra. So really your { "$size": 2 } condition is being ignored and only the second condition is applied.

A valid query form should look like this:

query: {
    "$and": [
        { "criteria" : { "$size" : 2 } },
        { "criteria" : { "$all": [{ "key1": "2015610" }, { "key2": "2096955" }] } }
    ]
}

As each set of conditions is specified within the array provided by $and the document structure of the query is valid and does not have a hash-key name overwriting the other. That's the proper way to write your two conditions, but there is a trick to making this work where the "upsert" is failing due to those conditions not matching a document. We need to overwrite what is happening when it tries to apply the $all arguments on creation:

update: {
    "$setOnInsert": {
        "criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
    },
    "$push": { "stats": { "id": "XXXX", "visited": 29, "liked": 144 } }
}

That uses $setOnInsert so that when the "upsert" is applied and a new document created the conditions specified here rather than using the field values set in the query portion of the statement are used instead.

Of course, if what you are really looking for is truly an exact match of the content in the array, then just use that for the query instead:

query: {
    "criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
}

Then MongoDB will be happy to apply those values when a new document is created and does not get confused on how to interpret the $all expression.

Upvotes: 2

Related Questions