I have an array in_cart which has product_id(s) and the amount of the individual items in the cart document
"in_cart":[
{
"product_id":"12345",
"amount":2
}
]
What I want is to do is insert the amount field into the details array. $lookup operator is done on the product_id so there will always be an equal amount of items in both arrays.
"details":[
{
"_id":"12345",
"name":"test",
"price":1110,
// ...more data...
}
]
$map to iterate over the details array.
$filter to filter out the document from in_cart array that has the same value of product_id field as the current item from details array have in _id field
$arrayElemAt to get the first element of the filtered array (since it will always have only one element)
$getField to get only amount property of filtered item
db.collection.aggregate([
{
$set: {
details: {
$map: {
input: "$details",
in: {
_id: "$$this._id",
name: "$$this.name",
price: "$$this.price",
amount: {
$getField: {
field: "amount",
input: {
$arrayElemAt: [
{
$filter: {
input: "$in_cart",
as: "cart_item",
cond: {
$eq: [
"$$cart_item.product_id",
"$$this._id"
]
}
}
},
0
]
}
}
}
}
}
}
}
}
])
Working example
Related
I have used the $group pipeline and inside it, I have used $addToSet which has created an array of objects which contains two values - {subGenre (string), flag (bool)}. In the next pipeline of $project, I need to run a loop through this array and I need to select only that element of the array where flag is false.
So my code looks like this:
let data = await Books.aggregate(
[
{
$group: {
_id: genre,
price: { $sum: "$price" },
data: {
$addToSet: {
subGenre: "$subGenre",
flag: "$flagSelectGenre"
}
}
}
}
]
);
This would return documents like:
_id: {
genre: "suspense",
},
price: 10210.6,
data: [
{
subGenre: "Thriller",
flag: false,
},
{
subGenre: "jumpScare",
flag: true,
}
{
subGenre: "horror",
flag: false,
}
]
After this, I need to run a $project pipeline where I have to only project that element of the data array where the flag is true. The flag will be true for only one element.
$project: {
price: "$price",
subGenre: {$...... } // some condition on data array??
}
The final output should look like this:
price: 10210.6,
subGenre: "jumpScare",
You can do it like this:
$filter - to filter items from data array, where flag property is equal to true.
$first - to get first item from above array.
$getField - to get value of subGenre property of the above item.
db.collection.aggregate([
{
"$project": {
"_id": 0,
"price": 1,
"data": {
"$getField": {
"field": "subGenre",
"input": {
"$first": {
"$filter": {
"input": "$data",
"cond": "$$this.flag"
}
}
}
}
}
}
}
])
Working example
You can use $filter array operator to loop and filter elements by required conditions,
$filter to iterate loop of data array, if flag is true then return element
$let to define a variable and store the above filter result
$first to return the first element from the filtered result, you can also use $arrayElemAt if you are using lower version of the MongoDB
{
$project: {
price: 1,
subGenre: {
$let: {
vars: {
data: {
$filter: {
input: "$data",
cond: "$$this.flag"
}
}
},
in: { $first: "$$data.subGenre" }
}
}
}
}
Playground
Another approach using $indexOfArray and $arrayElemAt operators,
$indexOfArray will find the matching element index of the array
$arrayElemAt to get specific element by specifying the index of the element
{
$project: {
price: 1,
subGenre: {
$arrayElemAt: [
"$data.subGenre",
{ $indexOfArray: ["$data.flag", true] }
]
}
}
}
Playground
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
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 need to update the name of field in a collection. The problem is that the field in question is within an array. So I'm trying to determine the correct way do this. I tried this to accomplish renaming the field that exists within the "plans" array: :
db.customers.updateMany( {}, { $rename: { "plans" : { "subscriptionType": "membershipType" } } } );
But this won't work. What's the correct way to handle this kind of transformation of a field within an array?
The data looks like this:
{
_id: 123,
prop1: value,
prop2: value,
prop3: value,
plans: [
subscriptionType: value,
otherProp: value,
otherProp: value
]
}
You can use Aggregation Framework's $addFields to override plans field and $map operator to rename field inside an array. Then you can use $out to override existing collection:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
membershipType: "$$plan.subscriptionType",
otherField: "$$plan.otherField",
otherField2: "$$plan.otherField2"
}
}
}
}
},
{
$out: "customers"
}
])
Alternatively you can do that dynamically. In this solution you don't have to explicitly specify other field names:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
$mergeObjects: [
{ membershipType: "$$plan.subscriptionType" },
{
$arrayToObject: {
$filter: {
input: { $objectToArray: "$$plan" },
as: "plan",
cond: { $ne: [ "$$plan.k", "subscriptionType" ] }
}
}
}
]
}
}
}
}
},
{
$out: "customers"
}
])
Using $objectToArray to $filter out old key-value pair and the using $mergeObjects to combine that filtered object with new renamed field.
I have a collection and each document in that collection has an array field countries. I want to select all documents which include any of below countries:
China, USA, Australia
And the output should show the number of above countries each document has.
I use below aggregate command:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {$size: '$countries'}
}
}
]);
it doesn't work as expected. It shows the number of all countries in the document who has the above-listed country. For example, if a document has China, Japan in its countries field, I expected it return 1 (because only China is in the above country list) but it returns two. How can I do that in the aggregation command?
The $in operator just "queries" documents that contain one of the possible values, so it does not remove anything from the array.
If you want to count "only matches" then apply $setIntersection to the array before $size:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {
$size: {
"$setIntersection": [["USA", 'China', 'Australia'], '$countries' ]
}
}
}
]);
That returns the "set" of "unique" matches to the array provided against the array in the document.
There is an alternate of $in as an aggregation operator in modern releases ( MongoDB 3.4 at least ). This works a bit differently in "testing" a "singular" value against an array of values. In array comparison you would apply with $filter:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {
$size: {
$filter: {
input: '$countries',
cond: { '$in': [ '$$this', ["USA", 'China', 'Australia'] ] }
}
}
}
}
]);
That really should only be important to you where the array "within the document" contains entries that are not unique. i.e:
{ countries: [ "USA", "Japan", "USA" ] }
And you needed to count 2 for "USA", as opposed to 1 which would be the "set" result of $setIntersection