Can mongo upsert array data?

存储架构 2018-09-16 阅读原文

I have a mongo document like this.

{
    "_id" : ObjectId("50b429ba0e27b508d854483e"),
    "array" : [
        {
            "id" : "1",
            "letter" : "a"
        },
        {
            "id" : "2",
            "letter" : "b"
        }
    ],
    "tester" : "tom"
}

I want to be able to insert and update the array
with a single mongo command and not use a conditional within a find()
then run insert()
and update()
depending on the presence of the object.

The id
is the item I want to be the selector. So if I update the array with this:

{
    "id" : "2",
    "letter" : "c"
}

I have to use a $set
statement

db.soup.update({
    "tester":"tom",
    'array.id': '2'
}, {
    $set: {
        'array.$.letter': 'c'
    }
})

And if I want to insert a new object into the array

{
    "id" : "3",
    "letter" : "d"
}

I have to use a $push
statement

db.soup.update({
    "tester":"tom"
}, {
    $push: {
        'array': {
            "id": "3",
            "letter": "d"
        }
    }
})

I need a sort of upsert for an array item.

Do I have to do this programmatically or can I do this with a single mongo call?

Problem courtesy of: ThomasReggi

Solution

I'm not aware of an option that would upsert into an embedded array as at MongoDB 2.2, so you will likely have to handle this in your application code.

Given that you want to treat the embedded array as sort of a virtual collection, you may want to consider modelling the array as a separate collection instead.

You can't do an upsert based on a field value within an embedded array, but you could use
$addToSet

to insert an embedded document if it doesn't exist already:

db.soup.update({
    "tester":"tom"
}, {
    $addToSet: {
        'array': {
            "id": "3",
            "letter": "d"
        }
    }
})

That doesn't fit your exact use case of matching by id
of the array element, but may be useful if you know the expected current value.

Solution courtesy of: Stennie

Discussion

You can achieve upserts of embedded document sets in some cases if you can restructure your documents to use objects instead of arrays. I just answered this on another question
but will adapt it here for convenience.

An example document would be

{
    tester: 'tom',
    items: {
        '1': 'a',
        '2': 'b'
    }
}

To upsert an item with id
3 and value 'c', you'd do

db.coll.update(
    { tester: 'tom' }, 
    { $set: { 'items.3': 'c' } }
)

One drawback is that querying for field names (using the $exists
query operator) requires scanning. You can get around this by adding an extra array field that stores the property names. This field can be indexed and queried normally. Upserts become

db.coll.update(
    { tester: 'tom' }, 
    { 
        $set: { 'items.3': 'c' },
        $addToSet: { itemNames: 3 }
    }
)

(Keep in mind $addToSet
is O(n).) When removing a property, you have to pull it from the array as well.

db.widgets.update(
    { tester: 'tom' }, 
    { 
        $unset: { 'items.3': true },
        $pull: { itemNames: 3 }
    }
)

Discussion courtesy of: G-Wiz

I just ran into this problem myself. I wasn't able to find a one-call solution, but I found a two-call solution that works when you have a unique value in your array elements
. Use the $pull
command first, which removes elements from an array, and then $push
.

db.soup.update({
    "tester":"tom"
}, {
    $pull: {
        'array': {
            "id": "3"
        }
    }
})
db.soup.update({
    "tester":"tom"
}, {
    $push: {
        'array': {
            "id": "3",
            "letter": "d"
        }
    }
})

This should work when the document doesn't exist, when the document exists but the entry in the array doesn't exist, and when the entry exists.

Again, this only works if you have something, like the id
field in this example, that should be unique across elements of the array.

Discussion courtesy of: Rob Watts

This recipe can be found in it's original form on Stack Over Flow
.

Node.js Recipes

责编内容by:Node.js Recipes阅读原文】。感谢您的支持!

您可能感兴趣的

Use node.js only for one of its modules to perform... I'd very much like to use the natural module (which seems to do fine nlp in javascript, which is rare) to ...
在 Azure 虚拟机上快速搭建 MongoDB 集群 MongoDB 是目前在 NoSQL 市场上非常受欢迎的一个数据库,本文介绍如何使用 Azure PowerShell 和 Azure CLI 在 Azure 虚拟机上搭建单节点 MongoDB(测试使用)和包含主从复制以及分片集群的多节...
3 Tools for Testing Microservices 3 Tools for Testing Microservices DZone's Guide to 3 Tools for Testing Microservices There are...
Test Clicks On Connected Components In Under 10 Li... Here’s a fairly benign component, that can give developers pause when its time to write unit tests for your components: ...
How effective the QA process can be? Hi All. I am a test engineer with around 2 years of experience. As a QA person, I am more concerned about deliverin...