Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to upsert a list with a single query?

Tags:

mongodb

upsert

I know this question has been asked before, but that's a different scenario. I'd like to have a collection like this:

{
    "_id" : ObjectId("4c28f62cbf8544c60506f11d"),
    "pk": 1,
    "forums": [{
        "pk": 1,
        "thread_count": 10, 
        "post_count": 20,
    }, {
        "pk": 2,
        "thread_count": 5, 
        "post_count": 24,
    }]
}

What I want to do is to upsert a "forum" item, incrementing counters or adding an item if it does not exist.

For example to do something like this (I hope it makes sense):

db.mycollection.update({
    "pk": 3,
    "forums.pk": 2
}, {
    "$inc": {"forums.$.thread_count": 1},
    "$inc": {"forums.$.post_count": 1},
}, true)

and have:

{
    "_id" : ObjectId("4c28f62cbf8544c60506f11d"),
    "pk": 1,
    "forums": [{
        "pk": 1,
        "thread_count": 10, 
        "post_count": 20,
    }, {
        "pk": 2,
        "thread_count": 5, 
        "post_count": 24,
    }]
},
{
    "_id" : ObjectId("4c28f62cbf8544c60506f11e"),
    "pk": 3,
    "forums": [{
        "pk": 2,
        "thread_count": 1, 
        "post_count": 1,
    }]
}

I can surely make it in three steps:

  1. Upsert the whole collection with a new item
  2. addToSet the forum item to the list
  3. increment forum item counters with positional operator

That's to say:

db.mycollection.update({pk:3}, {pk:3}, true)
db.mycollection.update({pk:3}, {$addToSet: {forums: {pk:2}}})
db.mycollection.update({pk:3, 'forums.pk': 2}, {$inc: {'forums.$.thread_counter': 1, {'forums.$.post_counter': 1}})

Are you aware of a more efficient way to do it? TIA, Germano

like image 956
Germano Avatar asked Aug 12 '10 12:08

Germano


People also ask

What is an upsert in SQL?

What is an Upsert in SQL? The term upsert is a portmanteau – a combination of the words “update” and “insert.” In the context of relational databases, an upsert is a database operation that will update an existing row if a specified value already exists in a table, and insert a new row if the specified value doesn’t already exist.

How to know which list to use for update and insert?

It's easy to found out which you should do since if there is an Id on the record it will always be update and the other way around for insert. You can even use 2 separate lists for this. 1 for update and 1 for insert.

How do I perform a basic upsert in PostgreSQL?

In the current version of PostgreSQL INSERT, we can achieve a basic upsert by specifying the conflict target (in this case id, the primary key column) and what we want to do if a conflict is detected (in this case, update the existing row):

How do I Upsert a key in MySQL?

The UPSERT command doesn’t exist in MySQL, but upserts can still be achieved. The best method for implementing an upsert in the current version of MySQL is INSERT … ON DUPLICATE KEY UPDATE . Let’s look at that command in a bit more detail. As the command itself suggests, INSERT …


1 Answers

As you may have discovered, the positional operator cannot be used in upserts:

The positional operator cannot be combined with an upsert since it requires a matching array element. If your update results in an insert then the "$" will literally be used as the field name.

So you won't be able to achieve the desired result in a single query.

You have to separate the creation of the document from the counter update. Your own solution is on the right track. It can be condensed into the following two queries:

// optionally create the document, including the array
db.mycollection.update({pk:3}, {$addToSet: {forums: {pk:2}}}, true)

// update the counters in the array item
db.mycollection.update({pk:3, 'forums.pk': 2}, {$inc: {'forums.$.thread_counter': 1, 'forums.$.post_counter': 1}})
like image 53
Niels van der Rest Avatar answered Sep 28 '22 03:09

Niels van der Rest