Search code examples
mongodbconditional-statementsprojectaggregation

Conditionally project a field value in mongodb


**I have a mongo document as below. **

{
  "metadata": {
    "docId": "7b96a"
  },
  "items": {
    "content": "abcd",
    "contentWithInfo": "content with additional info"
  }
}

I want to project content field based on the condition whether contentWithInfo field is present or not. If contentWithInfo is present, it's value should be projected as content field value and contentWithInfo should be empty. Otherwise content should be projected as is. Is it possible?

I tried the following shell query:

db.collection1.aggregate([
  {
    "$match": {
      "metadata.docId": {
        "$in": [
          "7b96a"
        ]
      }
    }
  },
  {
    "$unwind": "$items"
  },
  {
    "$project": {
      "metadata": 1,
      "items.content": {
        "$cond": {
          "if": {
            "$eq": [
              "$items.contentWithInfo",
              null
            ]
          },
          "then": "$items.content",
          "else": "$items.contentWithInfo"
        }
      }
    }
  }
])

If contentWithInfo is present, it is returning the following:

{
  "metadata": {
    "docId": "7b96a"
  },
  "items": {
    "content": "content with additional info"
  }
}

If contentWithInfo is not present, it is returning the following:

{
  "metadata": {
    "docId": "7b96a"
  },
  "items": {}
}

whereas I expect it to return

{
  "metadata": {
    "docId": "7b96a"
  },
  "items": {
    "content": "abcd"
  }
}

Solution

  • Approach 1

    Instead of checking items.contentWithInfo is null, check whether the items.contentWithInfo is missing with $type operator.

    db.collection.aggregate([
      {
        "$match": {
          "metadata.docId": {
            "$in": [
              "7b96a"
            ]
          }
        }
      },
      {
        "$unwind": "$items"
      },
      {
        "$project": {
          "metadata": 1,
          "items.content": {
            "$cond": {
              "if": {
                "$eq": [
                  {
                    $type: "$items.contentWithInfo"
                  },
                  "missing"
                ]
              },
              "then": "$items.content",
              "else": "$items.contentWithInfo"
            }        
          }
        }
      }
    ])
    

    Demo Approach 1 @ Mongo Playground

    Another approach, if you want items.content as default value if items.contentWithInfo is missing or null, you can use $ifNull operator.

    db.collection.aggregate([
      {
        "$match": {
          "metadata.docId": {
            "$in": [
              "7b96a"
            ]
          }
        }
      },
      {
        "$unwind": "$items"
      },
      {
        "$project": {
          "metadata": 1,
          "items.content": {
            $ifNull: [
              "$items.contentWithInfo",
              "$items.content"
            ]
          }
        }
      }
    ])
    

    Demo Approach 2 @ Mongo Playground