Andi Giga
Andi Giga

Reputation: 4162

MongoDB: Get Last Element of Array if Property Exists

I would like to aggregate the last setting with an existing value.

My data structure:

{
  _id: 1,
  ...(many other fields)...
  settings: [
    {
      _id: 11,
      values: [
        {
          likesFruit: true,
          likesMeat: true,
        }
      ] 
   },
   {
      _id: 12,
      values: [
        {
          likesFruit: false
        }
      ] 
    }
  ]
}, 
{
  _id: 2,
  ...(many other fields)...
  settings: [] // empty
}

Question: How to get this result

{
  _id: 1,
  ...(many other fields)...
  likesFruit: false,
  likesMeat: true
},
{
 _id: 2,
 ...(many other fields)...
}

I need a solution which doesn't remove the results where settings are e.g. empty/ not contain all props. E.g._id: 2 does not contain any settings. So I don't want to unwind it and filter it then for a specific setting because I still need the root object even if no setting exists.

I tried it with addFields:

$addFields: {
  likesFruit: "$settings.value.likesFruit",
  likesMeat: "$settings.value.likesMeat"
}

Which leaves me with this:

_id: 1,
...(many other fields)...
settings: [...],
likesFruit: [[true], [false]]
likesMeat: [[], [true]]

Which I tried to filter with:

$addFields: {
  likesFruit: {
    $filter: {
      input: "$likesMeat",
      as: "items",
      cond: { $ne : ["$$items", null ] }
    }
  }
}

My main problem is the double nesting. The "settings.value" I'm interested in has only one object inside. So I guess going from settings.values: [{}] to settings.values: {} would be a key to the solution. Then I could project the single values, use $reverseArray and $arrayElemAt: [$array, 0] to get the last element.

Upvotes: 1

Views: 401

Answers (1)

mickl
mickl

Reputation: 49945

You can take advantage of $mergeObjects operator since as the documentation states:

mergeObjects overwrites the field values as it merges the documents. If documents to merge include the same field name, the field, in the resulting document, has the value from the last document merged for the field.

As you have nested arrays you need double $reduce to aggregate your data. You can also use $replaceRoot to promote your aggregated values to the top level

db.col.aggregate([
    {
        $addFields: {
            settingsAggregated: {
                $reduce: {
                    input: "$settings",
                    initialValue: {},
                    in: {
                        $mergeObjects: [ "$$value", 
                            { $reduce: { input: "$$this.values", initialValue: {}, in: { $mergeObjects: [ "$$value", "$$this" ] } } }
                        ]
                    }
                }
            }
        }
    },
    {
        $replaceRoot: {
            newRoot: {
                $mergeObjects: [ "$$ROOT", "$settingsAggregated" ]
            }
        }
    },
    {
        $project: {
            settings: 0,
            settingsAggregated: 0
        }
    }
])

Mongo Playground

Upvotes: 1

Related Questions