How to compare two fields of one subdocument in array? - mongodb

Given some documents in MongoDB
[
{
"id": 1,
"items": [
{
"id": "T0001",
"x": 10,
"y": 10
},
{
"id": "T0002",
"x": 10,
"y": 5
},
{
"id": "T0003",
"x": 10,
"y": 10
},
{
"id": "T0004",
"x": 10,
"y": 20
}
]
},
{
"id": 2,
"items": [
{
"id": "T0001",
"x": 10,
"y": 5
},
{
"id": "T0002",
"x": 10,
"y": 15
}
]
}
]
I would like to remove the subdocuments with items.id="T0001" and items.id="T0002" in document id=1. This could be done with the following command.
db.collection.update({
id: 1
},
{
$pull: {
items: {
id: {
$in: [
"T0001",
"T0002"
]
}
}
}
})
However, if I would like to add one more condition "items.x === items.y" into the $pull operation. (i.e. only the subdocument whose id="T0001" will be removed because its x=10 and y=10)
The sample code can be found here https://mongoplayground.net/p/rYJp8wQPpcS.
Could anyone show me some tips to solve this problem? Thank you!

Working on the update with the aggregation pipeline will be easier.
$set - Set the items field.
1.1. $filter - Filter the items array by condition:
1.1.1. $not - Negate the result of 1.1.1.1.
1.1.1.1 $and - Fulfill both conditions of id in ["T0001", "T0002"] and x and y are equal.
db.collection.update({
id: 1
},
[
{
$set: {
items: {
$filter: {
input: "$items",
cond: {
$not: {
$and: [
{
$in: [
"$$this.id",
[
"T0001",
"T0002"
]
]
},
{
$eq: [
"$$this.x",
"$$this.y"
]
}
]
}
}
}
}
}
}
])
Sample Mongo Playground

Related

Push object in mongoDB array of objects which contains value from parent document

So I have a mongodb document
{
"id": 1,
"balance": 10,
"transactions": [
{
"balance": 10,
"transaction_amount": 10
}
]
}
I want to add another object inside of transactions array and that object uses previous balance and adds the newer value.
For eg, transaction amount is 20
the document should be updated in this way
{
"id": 1,
"balance": 30,
"transactions": [
{
"balance": 10,
"transaction_amount": 10
},
{
"balance": 30,
"transaction_amount": 20
}
]
}
Now if I want to add one more document in transactions array with transaction_amount as 5 the resultant document becomes
{
"id": 1,
"balance": 35,
"transactions": [
{
"balance": 10,
"transaction_amount": 10
},
{
"balance": 30,
"transaction_amount": 20
},
{
"balance": 35,
"transaction_amount": 5
}
]
}
Is there any way to do this using aggregation pipeline
Simply use $add and $concatArrays to wrangle the data
db.collection.update({
id: 1
},
[
{
"$addFields": {
"txnAmt": <amount you want>
}
},
{
$addFields: {
balance: {
$add: [
"$balance",
"$txnAmt"
]
}
}
},
{
"$addFields": {
"transactions": {
"$concatArrays": [
"$transactions",
[
{
balance: "$balance",
transaction_amount: "$txnAmt"
}
]
]
}
}
},
{
"$unset": "txnAmt"
}
],
{
multi: true
})
Here is the Mongo playground for your reference.

Query maximum N records of each group base on a condition in MongoDB?

I have a question regarding querying data in MongoDB. Here is my sample data:
{
"_id": 1,
"category": "fruit",
"userId": 1,
"name": "Banana"
},
{
"_id": 2,
"category": "fruit",
"userId": 2,
"name": "Apple"
},
{
"_id": 3,
"category": "fresh-food",
"userId": 1,
"name": "Fish"
},
{
"_id": 4,
"category": "fresh-food",
"userId": 2,
"name": "Shrimp"
},
{
"_id": 5,
"category": "vegetable",
"userId": 1,
"name": "Salad"
},
{
"_id": 6,
"category": "vegetable",
"userId": 2,
"name": "carrot"
}
The requirements:
If the category is fruit, returns all the records match
If the category is NOT fruit, returns maximum 10 records of each category grouped by user
The category is known and stable, so we can hard-coded in our query.
I want to get it done in a single query. So the result expected should be:
{
"fruit": [
... // All records of
],
"fresh-food": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "fresh-food"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "fresh-food"
]
},
...
],
"vegetable": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "vegetable"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "vegetable"
]
},
]
}
I've found the guideline to group by each group using $group and $slice, but I can't apply the requirement number #1.
Any help would be appreciated.
You need to use aggregation for this
$facet to categorize incoming data, we categorized into two. 1. Fruit and 2. non_fruit
$match to match the condition
$group first group to group the data based on category and user. Second group to group by its category only
$objectToArray to make the object into key value pair
$replaceRoot to make the non_fruit to root with fruit
Here is the code
db.collection.aggregate([
{
"$facet": {
"fruit": [
{ $match: { "category": "fruit" } }
],
"non_fruit": [
{
$match: {
$expr: {
$ne: [ "$category", "fruit" ]
}
}
},
{
$group: {
_id: { c: "$category", u: "$userId" },
data: { $push: "$$ROOT" }
}
},
{
$group: {
_id: "$_id.c",
v: {
$push: {
uerId: "$_id.u",
data: { "$slice": [ "$data", 3 ] }
}
}
}
},
{ $addFields: { "k": "$_id", _id: "$$REMOVE" } }
]
}
},
{ $addFields: { non_fruit: { "$arrayToObject": "$non_fruit" } }},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$$ROOT", "$non_fruit" ]
}
}
},
{ $project: { non_fruit: 0 } }
])
Working Mongo playground

How to sort and find three largest elements in array?

I want generate report from mongodb. I have a aggregation which generated:
[{"_id": {
"m": 1,
"y": 2020
},
"meals": [
{
"name": "Sandwich",
"servings": 2
},
{
"name": "Fish",
"servings": 7
},
{
"name": "Pizza",
"servings": 3
},
{
"name": "Beef",
"servings": 3
},
{
"name": "Soup",
"servings": 3
}]},
{"_id": {
"m": 12,
"y": 2018
},
"meals": [
{
"name": "Beef",
"servings": 1
},
{
"name": "Spaghetti",
"servings": 2
}]}]
I need to get 3 elements where servings the largest and sort them. If elements are not three, just sort. For example:
[{"_id": {
"m": 1,
"y": 2020
},
"meals": [
{
"name": "Fish",
"servings": 7
},
{
"name": "Pizza",
"servings": 3
},
{
"name": "Beef",
"servings": 3
}]},
{"_id": {
"m": 12,
"y": 2018
},
"meals": [
{
"name": "Spaghetti",
"servings": 2
},
{
"name": "Beef",
"servings": 1
}
]}]
I can't use find(), because I want to do this in aggregation. I tried to use $filter, but I am doing something wrong.
$unwind deconstruct meals array
$sort by servings in descending order
$group by _id and reconstruct meals array
$slice to get first 3 elements from meals
db.collection.aggregate([
{ $unwind: "$meals" },
{ $sort: { "meals.servings": -1 } },
{
$group: {
_id: "$_id",
meals: { $push: "$meals" }
}
},
{
$project: {
meals: { $slice: ["$meals", 3] }
}
}
])
Playground
It is very important to sort and find data from the servers by mongodb.
So I think that you should use the some commands as cooperating.
I mean in your case, you use that
find().sort().limit(3)
I hope that it would help you.
$unwind and $group are expensive.
Starting form version >= 4.4, MongoDB supports functions. You can use it in combination with slice to get the desired result.
db.collection.aggregate([
{
"$project": {
"meals": {
"$slice": [
{
"$function": {
"body": "function(updates) { updates.sort((a, b) => b.servings - a.servings); return updates;}",
"args": [
"$meals"
],
"lang": "js"
}
},
3
],
},
},
},
])
Mongo Playground Sample Execution

Mongodb Update subdocument values matching subdocument and without altering other fields

I have a schema like this
{
"_id": "5f86da4b5bb9a62742371409",
"status" : true,
"doc": [
{
"_id": "5f86cacbe4d7c423f21faf50",
"a": 4,
"b": null
},
{
"_id": "5f86cb01a1ca5124063299c1",
"a": 6,
"b": null
}
]
}
And I have an update in the form of
[
{
"_id": "5f86cacbe4d7c423f21faf50",
"b": 90
},
{
"_id": "5f86cb01a1ca5124063299c1",
"b": 45
}
]
How can I update the collection to end up like this?
{
"_id": "5f86da4b5bb9a62742371409",
"status" : true,
"doc": [
{
"_id": "5f86cacbe4d7c423f21faf50",
"a": 4,
"b": 90
},
{
"_id": "5f86cb01a1ca5124063299c1",
"a": 6,
"b": 45
}
]
}
Basically I want to update the subdocument with specific keys only (leaving other keys intact)
You can use update with aggregation pipeline from MongoDB 4.2,
$map to iterate loop of array doc and inside it iterate through updateDocs array, it will return matching b, and then $mergeObjects will return updated document,
let updateDocs = [
{ "_id": mongoose.Types.ObjectId("5f86cacbe4d7c423f21faf50"), "b": 90 },
{ "_id": mongoose.Types.ObjectId("5f86cb01a1ca5124063299c1"), "b": 45 }
];
db.collection.updateMany({},
[
{
$set: {
doc: {
$map: {
input: "$doc",
as: "d",
in: {
$mergeObjects: [
"$$d",
{
$reduce: {
input: updateDocs,
initialValue: {},
in: {
$cond: [
{ $eq: ["$$this._id", "$$d._id"] },
{ b: "$$this.b" },
"$$value"
]
}
}
}
]
}
}
}
}
}
]
)
Playground

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here