I have the following situation in my aggregation: At some point my documents are holding an array of objects and an array of the same length at the root level. An example would be:
{_id:0,
cars:[{name:"vw polo",door:3},{name:"vw golf",door:5}],
possible_colors:[["black","blue"],["white"]]}
What I'm trying now is to update project the possible colors into each object of the cars array. The expected result should look as follows.
{_id:0,
cars:[{name:"vw polo",door:3,possible_colors:["black","blue"]},
{name:"vw golf",door:5,possible_colors:["white"]}],
}
I already tried {$addfields:{cars:{$zip:[$cars,$possible_colors]}}}, but this creates a list of arrays, where each array contains the object and the correct subarray, but i was not able to merge them.
The $zip operator is a good approach however you need additional $map along with $mergeObjects in order to get the desired structure:
db.collection.aggregate([
{
$addFields: {
cars: {
$map: {
input: { $zip: { inputs: [ "$cars", "$possible_colors" ] } },
in: {
$mergeObjects: [
{ $arrayElemAt: [ "$$this", 0 ] },
{ possible_colors: { $arrayElemAt: [ "$$this", 1 ] } }
]
}
}
}
}
}
])
Mongo Playground
Related
I have a collection with documents like this one:
{
f1: {
firstArray: [
{
secondArray: [{status: "foo1"}, {status: "foo2"}, {status: "foo3"}]
}
]
}
}
My expected result includes documents that have at least one item in firstArray, which is last object status on the secondArray is included in an input array of values (eg. ["foo3"]).
I don't must use aggregate.
I tried:
{
"f1.firstArray": {
$elemMatch: {
"secondArray.status": {
$in: ["foo3"],
},
otherField: "bar",
},
},
}
You can use an aggregation pipeline with $match and $filter, to keep only documents that their size of matching last items are greater than zero:
db.collection.aggregate([
{$match: {
$expr: {
$gt: [
{$size: {
$filter: {
input: "$f1.firstArray",
cond: {$in: [{$last: "$$this.secondArray.status"}, ["foo3"]]}
}
}
},
0
]
}
}
}
])
See how it works on the playground example
If you know that the secondArray have always 3 items you can do:
db.collection.find({
"f1.firstArray": {
$elemMatch: {
"secondArray.2.status": {
$in: ["foo3"]
}
}
}
})
But otherwise I don't think you can check only the last item without an aggregaation. The idea is that a regular find allows you to write a query that do not use values that are specific for each document. If the size of the array can be different on each document or even on different items on the same document, you need to use an aggregation pipeline
so imagine I have the following document:
{
"_id":...,
"data":{"a":[],"b":[],"x":[]}
}
I don't know beforehand which fields the subdocument data may have. I just know that every field in that subdocument will be an array
How do I make an update so that the object results like:
{
"_id":...,
"data":{"a":[1],"b":[1],"x":[1]}
}
Constraint: Using only mongodb operators. One single update. Without knowing the fields inside the 'data' subdocument
db.collection.update({},
[
{
$set: {
data: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$data" },
as: "d",
in: { k: "$$d.k", v: [ 1 ] }
}
}
}
}
}
])
mongoplayground
I have a huge array and I need to get only array indexes that are multiple by 12. I thought using $filter, but I'm struggling in the cond parameter.
I'm using aggregate because there is more stuff that i need to do, but I'm stuck in this filter part.
data : [3,4,5,6,...,etc, <more 5000 items> ]
query:
db.test.aggregate([{$project:{filtered_values: {$filter:{input: '$data', as:data ,cond: { ??? some witchcraft ???} }}}}])
Expected result:
[data[0],data[12],data[24],data[36],data[i%12 ===0]]
How can i reach this result with aggregate? Or if is $filter the best solution?
Try this:
db.collection.aggregate([
{
$set: {
data: {
$map: {
input: { $range: [0, { $size: "$data" }, 12] },
as: "i",
in: { $arrayElemAt: ["$data", "$$i"] }
}
}
}
}
])
Below is an example structure of a document:
{
userName:"herobrewer555",
recentBrews:[{_id:1, name:'Watermelon wine', wortRating:'D6Z'}, {_id:2, name:'Pineapple wine', wortRating:'A5Z'}, {_id:3, name:'Banana mead', wortRating:'C6U'}],
brewDetails:[{_id:1, mainIngredient:'watermelon', sugarSource:"white sugar"}, {_id:2, mainIngredient:'pineapple', sugarSource:"white sugar"}, {_id:3, mainIngredient:'banana', sugarSource:"honey"}]
}
Is it possible to merge objects inside recentBrews and brewDetails based on their _id like below?
{
userName:"herobrewer555",
recentBrews:[{_id:1, mainIngredient:'watermelon', sugarSource:"white sugar", name:'Watermelon wine', wortRating:'D6Z'}, {_id:2, mainIngredient:'pineapple', sugarSource:"white sugar", name:'Pineapple wine', wortRating:'A5Z'},{_id:3, mainIngredient:'banana', sugarSource:"honey", name:'Banana mead', wortRating:'C6U'}]
}
In some cases there is an index correspondence between the two arrays, i.e, the first object in recentBrews and the first object in brewDetails will always refer the same brew.
$map to iterate loop of recentBrews array
$filter get matching object from brewDetails array
$arrayElemAt to get first element from above $filter result
$mergeObjects to merge current object and returned object from $arrayElemAt
db.collection.aggregate([
{
$project: {
userName: 1,
recentBrews: {
$map: {
input: "$recentBrews",
as: "r",
in: {
$mergeObjects: [
"$$r",
{
$arrayElemAt: [
{
$filter: {
input: "$brewDetails",
cond: { $eq: ["$$this._id", "$$r._id"] }
}
},
0
]
}
]
}
}
}
}
}
])
Playground
I can't get my head around a mongodb aggregation framework construction that adds up some values for each "_id" field documents... IF those values exist for the field "Wert".
E.g I have a document with _id field and a conditional ProduktTeilsummeDemonstrator":[] or "ProduktTeilsummeDemonstrator":[{Wert:342},{Wert:142}] that array can be empty or not, if it is empty, I want to add a new field "ProduktTeilsumme":0, else, I want to add up all values in that array to the new field...
The data that I have looks like this:
[{"_id":230,"ProduktSummeDemonstrator":713,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null},{"_id":855,"ProduktSummeDemonstrator":1744,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null},{"_id":767,"ProduktSummeDemonstrator":1010,"ProduktTeilsummeDemonstrator":[{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":24},{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":102},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":15},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":20},{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":90},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":104},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":29},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":94},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":33},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":245},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":243},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":11}],"ProduktTeilsumme":null},{"_id":9,"ProduktSummeDemonstrator":94,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null}]
I tried out different things with $reduce or $cond expressions, but somehow it won't add up: (Previously before that calculation stage I am grouping by ID and also filtering based on some time field condition..)
{
$project: {
ProduktSummeDemonstrator: "$ProduktSummeDemonstrator",
ProduktTeilsummeDemonstrator: {
$filter: {
input: "$res",
as: "res",
cond: { $and: [
{ $gte: ["$$res.Zeitstempel", new Date(req.params.start) ] },
{ $lte: ["$$res.Zeitstempel", new Date(req.params.end) ] }
] }
}
},
ProduktTeilsumme: {/*
$reduce: {
input: "$ProduktTeilsummeDemonstrator",
initialValue:0,
in: {
$add: ["$$value","$$this.Wert"]
}
} */
$cond: {
if: { $eq: [ "", "$ProduktTeilsummeDemonstrator" ] },
then: 0,
else: {
$reduce: {
input: "$ProduktTeilsummeDemonstrator",
initialValue: 0,
in: {
$add: ["$$value","$$this.Wert"]
}
}
}
}
}
}
}
at least for "_id":767 I should get some values back, but I am getting "null" always.
You have to use multiple project stages if you want to keep both the array and added value. One for $filtering ProduktTeilsummeDemonstrator followed by adding up array values.
Something like
[
{"$project":{
"ProduktSummeDemonstrator":1,
"ProduktTeilsummeDemonstrator":{
"$filter":{
"input":"$ProduktTeilsummeDemonstrator",
"as":"res",
"cond":{
"$and":[
{"$gte":["$$res.Zeitstempel", new Date(req.params.start)]},
{"$lte":["$$res.Zeitstempel", new Date(req.params.end)]}
]
}
}
}
}},
{"$project":{
"ProduktSummeDemonstrator":1,
"ProduktTeilsummeDemonstrator":1,
"ProduktTeilsumme":{"$sum":"$ProduktTeilsummeDemonstrator.Wert"}
}}
]