skip to Main Content

I have a database with data like this:

[{
  "_id": {
    "$oid": "664baf560b9539e48fbc6ed1"
  },
  "name": "first assignment",
  "next": {
    "$oid": "664baf620b9539e48fbc6ed2"
  },
  "preview": null,
  "date": {
    "$date": "2024-04-20T10:00:00.000Z"
  }
},
{
  "_id": {
    "$oid": "664baf620b9539e48fbc6ed2"
  },
  "name": "second assignment",
  "next": {
    "$oid": "664baf6d0b9539e48fbc6ed3"
  },
  "preview": {
    "$oid": "664baf560b9539e48fbc6ed1"
  },
  "date": {
    "$date": "2024-04-21T10:00:00.000Z"
  }
},
{
  "_id": {
    "$oid": "664baf6d0b9539e48fbc6ed3"
  },
  "name": "third assignment",
  "next": {
    "$oid": "664baf8d0b9539e48fbc6ed4"
  },
  "preview": {
    "$oid": "664baf620b9539e48fbc6ed2"
  },
  "date": {
    "$date": "2024-04-22T10:00:00.000Z"
  }
},
{
  "_id": {
    "$oid": "664baf8d0b9539e48fbc6ed4"
  },
  "name": "fourth assignment",
  "next": null,
  "preview": {
    "$oid": "664baf6d0b9539e48fbc6ed3"
  },
  "date": {
    "$date": "2024-04-23T10:00:00.000Z"
  }
}
]

The next and preview fields refer to other documents in the same collection.
So, suppose I receive the ID of fourth assignment (664baf8d0b9539e48fbc6ed4). I need to create a new document with the same structure in which preview is the ID of the fourth assignment but also update the fourth assignment document to assign the ID of the new created document to next.
Is this flow possible in an aggregation or should I use traditional operations separately?

2

Answers


  1. If you can deduce the content of 5th assignment when you have received the id of 4th assignment, you can $set the next field of 4th assignment and $unionWith the 5th assignment you have in a $documents stage. Finally do a $merge to update the 2 documents into the collection.

    db.collection.aggregate([
      {
        "$match": {
          // id of 4th assignment
          "_id": {
            "$oid": "664baf8d0b9539e48fbc6ed4"
          }
        }
      },
      {
        $set: {
          next: {
            // id of 5th assignment
            "$oid": "664baf8d0b9539e48fbc6ed5"
          }
        }
      },
      {
        "$unionWith": {
          "coll": "collection",
          "pipeline": [
            {
              "$documents": [
                // 5th assignment here
                {
                  "_id": {
                    "$oid": "664baf8d0b9539e48fbc6ed5"
                  },
                  "name": "fifth assignment",
                  "next": null,
                  "preview": {
                    // id of 4th assignment
                    "$oid": "664baf8d0b9539e48fbc6ed4"
                  },
                  "date": "$$NOW"
                }
              ]
            }
          ]
        }
      },
      {
        "$merge": {
          "into": "collection",
          "on": "_id"
        }
      }
    ])
    

    Mongo Playground

    Login or Signup to reply.
  2. If you’re okay to create the ObjectId of the new "fifth" document in your application code in PyMongo, Mongoose, etc. (instead of letting it be set by MongoDB upon insert) then this can be done in a single aggregation pipeline.

    $merge will update fields of existing docs and create new docs for those which don’t exist.

    • For the "fourth" doc we need the _id to identify the existing doc and only the next field with the updated value.
    • For the new "fifth" doc, it needs to have all the fields, and in this case, also _id; and this will get inserted into the collection.

    What I’ve done below is:

    1. Create an Array of two docs, one for the existing "fourth" with updated values and one for the new "fifth" with all fields.

      • I have separated this into two stages; first create placeholder fields for existing & new, then put them into an array.
    2. By using $mergeObjects, the fifth doc can inherit all the fields of the fourth and then specify only the one’s we want to override – _id, preview, name, date.

      • next is already null from the current value of "fourth" in this stage so remains null in the new doc
      • Note that otherField: "other value" is not specified for the fifth but is inherited from the fourth.
      • If you don’t require inheriting ‘existing fields without changes’ and will specify all fields anyway, then use the $unionWith method in Ray’s answer.
    3. Unwind the array to create two docs; replace the placeholders as the docs; merge.

    db.collection.aggregate([
      // find fourth doc
      { $match: { _id: ObjectId("664baf8d0b9539e48fbc6ed4") } },
      {
        // can be combined with the next stage;
        // separate is easier to read & debug
        $project: {
          _id: 0,
          existing: {
            // references id of existing fourth
            _id: "$_id",
            // id of new fifth to be created
            next: ObjectId("664baf8d0b9539e48fbc6ed5")
          },
          new_doc: {
            $mergeObjects: [
              "$$ROOT",
              {
                // id of new fifth to be created
                _id: ObjectId("664baf8d0b9539e48fbc6ed5"),
                // name for fifth here
                name: "fifth",
                // id of existing fourth
                preview: ObjectId("664baf8d0b9539e48fbc6ed4"),
                date: "$$NOW"
              }
            ]
          }
        }
      },
      { $project: { write_objects: ["$existing", "$new_doc"] } },
      { $unwind: "$write_objects" },
      { $replaceWith: "$write_objects" },
      { $merge: "collection" }
    ])
    

    Mongo Playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search