Update document if value there is no match

Question:

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.

Asked By: Adders

||

Answers:

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"}}})
Answered By: styvane

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"}
}
Answered By: Tiramisu

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

Answered By: Tom Slabbaert