Adders
Adders

Reputation: 665

Update document if value there is no match

In Mongodb, how do you skip an update if one field of the document exists?

To give an example, I have the following document structure, and I'd like to only update it if the link key is not matching.

{
    "_id": {
        "$oid": "56e9978732beb44a2f2ac6ae"
    },
    "domain": "example.co.uk",
    "good": [
        {
            "crawled": true,
            "added": {
                "$date": "2016-03-16T17:27:17.461Z"
            },
            "link": "/url-1"
        },
        {
            "crawled": false,
            "added": {
                "$date": "2016-03-16T17:27:17.461Z"
            },
            "link": "url-2"
        }

    ]
}

My update query is:

links.update({
    "domain": "example.co.uk"
    },
    {'$addToSet':
        {'good':
            {"crawled": False, 'link':"/url-1"} }}, True)

Part of the problem is the crawl field could be set to True or False and the date will also always be different - I don't want to add to the array if the URL exists, regardless of the crawled status.

Update: Just for clarity, if the URL is not within the document, I want it to be added to the existing array, for example, if /url-3 was introduced, the document would look like this:

{
    "_id": {
        "$oid": "56e9978732beb44a2f2ac6ae"
    },
    "domain": "example.co.uk",
    "good": [
        {
            "crawled": true,
            "added": {
                "$date": "2016-03-16T17:27:17.461Z"
            },
            "link": "/url-1"
        },
        {
            "crawled": false,
            "added": {
                "$date": "2016-03-16T17:27:17.461Z"
            },
            "link": "url-2"
        },
        {
            "crawled": false,
            "added": {
                "$date": "2016-04-16T17:27:17.461Z"
            },
            "link": "url-3"
        }

    ]
}

The domain will be unique and specific to the link and I want it to insert the link within the good array if it doesn't exist and do nothing if it does exist.

Upvotes: 4

Views: 1356

Answers (3)

Tom Slabbaert
Tom Slabbaert

Reputation: 22276

The accepted answer is not correct by saying the only way to do it is using findOne first.

You can do it in a single db call by using the aggregation pipelined updates feature, this allows you to use aggregation operators within an update, now the strategy will be to concat two arrays, the first array will always be the "good" array, the second array will either be [new link] or an empty array based on the condition if the links exists or not using $cond, like so:

links.update({
  "domain": "example.co.uk"
},
[
  {
    "$set": {
      "good": {
        "$ifNull": [
          "$good",
          []
        ]
      }
    }
  },
  {
    "$set": {
      "good": {
        "$concatArrays": [
          "$good",
          {
            "$cond": [
              {
                "$in": [
                  "/url-1",
                  "$good.link"
                ]
              },
              [],
              [
                {
                  "crawled": False,
                  "link": "/url-1"
                }
              ]
            ]
          }
        ]
      }
    }
  }
], True)

Mongo Playground

Upvotes: 0

Sede
Sede

Reputation: 61225

The only way to do this is to find if there is any document in the collection that matches your criteria using the find_one method, also you need to consider the "good.link" field in your filter criteria. If no document matches you run your update query using the update_one method, but this time you don't use the "good.link" field in your query criteria. Also you don't need the $addToSet operator as it's not doing anything simple use the $push update operator, it makes your intention clear. You also don't need to "upsert" option here.

if not link.find_one({"domain": "example.co.uk", "good.link": "/url-1"}):
    link.update_one({"domain": "example.co.uk"}, 
                    {"$push": {"good": {"crawled": False, 'link':"/url-1"}}})

Upvotes: 2

Daniele Graziani
Daniele Graziani

Reputation: 496

in your find section of the query you are matching all documents where

"domain": "example.co.uk"

you need to add that you don't want to match

'good.link':"/url-1"

so try

{
    "domain": "example.co.uk",
    "good.link": {$ne: "/url-1"}
}

Upvotes: 1

Related Questions