1

I have some data in a MongoDB collection that looks something like this:

db.test.insertOne(
    { "interactions": [
            {
                data: "keep",
                prompt: "prompt 1"
            },
            {
                other: "keep",
                prompt: "prompt 2"
            },
            {
                field: "no prompt"
            }
        ]}
    )

I want to iterate over all the interactions, and set prompts to an array containing the current value of prompt. Something like this:

{
  "interactions": [
    {
      "data": "keep",
      "prompt": "prompt 1",
      "prompts": [ "prompt 1" ]
    },
    {
      "other": "keep",
      "prompt": "prompt 2",
      "prompts": [ "prompt 2" ]
    },
    {
      "field": "no prompt"
    }
  ]
}

I've been trying to do a updateMany() with an aggregation pipeline, but am getting an error that I don't understand. This is my update:

db.test.updateMany(
    {},
    [{
        $set: {
            interactions: {
                $map: {
                    input: "$interactions",
                    as: "interaction",
                    in: {
                        $mergeObjects: [
                            "$$interaction",
                            {
                                $cond: {
                                    if: { "$$interaction.prompt": { $exists: true } },
                                    then: {
                                        prompts: [ "$$interaction.prompt" ]
                                    },
                                    else: {}
                                }
                            }
                        ]
                    }
                }
            }
        }
    }]
    )

Running this I get an error:

Unrecognized expression '$$interaction.prompt'.

Running the update without the $cond works:

db.test.updateMany(
    {},
    [{
        $set: {
            interactions: {
                $map: {
                    input: "$interactions",
                    as: "interaction",
                    in: {
                        $mergeObjects: [
                            "$$interaction",
                            {
                                prompts: [ "$$interaction.prompt" ]
                            }
                        ]
                    }
                }
            }
        }
    }]
    )

but after that operation, the array element that didn't have a prompt has an array with a null value:

{
  "interactions": [
    {
      "data": "keep",
      "prompt": "prompt 1",
      "prompts": ["prompt 1"]
    },
    {
      "other": "keep",
      "prompt": "prompt 2",
      "prompts": ["prompt 2"]
    },
    {
      "field": "no prompt",
      "prompts": [null]
    }
  ]
}

I don't want prompts set on an element that didn't have prompt.

So why can't I access $$interactions.prompt within the $cond? Alternatively, if there's a better way to accomplish what I want to do, please let me know.

Yong Shun
  • 35,286
  • 4
  • 24
  • 46
Bernie
  • 2,253
  • 1
  • 16
  • 18

2 Answers2

3

In the aggregation pipeline, you can't use the $exists operator.

And you must start with the $ operator but not with variable $$.

Instead, you check whether $$interaction.prompt is neither undefined nor null.

db.collection.updateMany({},
[
  {
    $set: {
      interactions: {
        $map: {
          input: "$interactions",
          as: "interaction",
          in: {
            $mergeObjects: [
              "$$interaction",
              {
                $cond: {
                  if: {
                    $not: {
                      $in: [
                        "$$interaction.prompt",
                        [
                          undefined,
                          null
                        ]
                      ]
                    }
                  },
                  then: {
                    prompts: [
                      "$$interaction.prompt"
                    ]
                  },
                  else: {}
                }
              }
            ]
          }
        }
      }
    }
  }
])

Demo @ Mongo Playground

Yong Shun
  • 35,286
  • 4
  • 24
  • 46
  • 1
    Thanks, I can see that your answer works perfectly in the Mongo Playground. However, when I run it in my database, it's still adding the array with `null` for elements that don't have `prompt`. I don't understand what's different. Do you have any clues as to why that would be? I'm using MongoDB version 6.0.1. – Bernie Feb 16 '23 at 06:06
  • 1
    Using `$gt` instead of `$not, $in` works, as explained here: https://stackoverflow.com/a/25515046/1303158. I'll add another answer with my final solution, but will give you the points. Thanks again. – Bernie Feb 16 '23 at 06:18
  • @Bernie, you are welcome. Glad to see that you found the answer. – Yong Shun Feb 16 '23 at 06:56
0

For some reason, using $not and $in didn't work in my database, although it did in the Playground. After finding an alternative here: https://stackoverflow.com/a/25515046/1303158, my final solution is:

db.collection.update({},
[
  {
    $set: {
      interactions: {
        $map: {
          input: "$interactions",
          as: "interaction",
          in: {
            $mergeObjects: [
              "$$interaction",
              {
                $cond: {
                  if: {
                    $gt: [
                      "$$interaction.prompt",
                      null
                    ]
                  },
                  then: {
                    prompts: [
                      "$$interaction.prompt"
                    ]
                  },
                  else: {}
                }
              }
            ]
          }
        }
      }
    }
  }
],
{
  multi: true
})

Demo @ Mongo Playground

Bernie
  • 2,253
  • 1
  • 16
  • 18