Remove Some array elements based on a condition and update size of array as one more filed in mongo - mongodb

I have following collection
[
{
"_id": ObjectId("57315ba4846dd82425ca2408"),
"myarray": [
{
"point": 5,
"userId": "570ca5e48dbe673802c2d035"
},
{
"point": 2,
"userId": "613ca5e48dbe673802c2d521"
},
{
"point": 4,
"userId": "570ca5e48dbe673802c2d045"
},
{
"point": 4,
"userId": "570ca5e48dbe473802c2d035"
}
]
}
]
I have a collection like above and I want to remove some objects inside array based on userID condition and after removing I have to update one field in mongo with size of array
I'm trying with the below query where removing array elements is working as excepted but array size is not updating properly
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
{
$pull: {
"myarray": {
userId: {
$in: [
"570ca5e48dbe673802c2d035",
"613ca5e48dbe673802c2d521"
]
}
}
},
"$set": {
profilecount: {
$size: "$myarray"
}
}
})
to see result of query please click this link and run query https://mongoplayground.net/p/FtMk7ymacr3

One option is using an update with a pipeline:
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
[{
$set: {
"myarray": {
$filter: {
input: "$myarray",
cond: {
$not: {
$in: [
"$$this.userId",
["570ca5e48dbe673802c2d035", "613ca5e48dbe673802c2d521"]
]
}
}
}
}
}
},
{$set: {profilecount: {$size: "$myarray"}}}
])
See how it works on the playground example

Related

How do I use $unwind and then $group in the same mongodb query

I have the following mongodb structure...
[
{
track: 'Newcastle',
time: '17:30',
date: '22/04/2022',
bookmakers: [
{
bookmaker: 'Coral',
runners: [
{
runner: 'John',
running: true,
odds: 3.2
},
...
]
},
...
]
},
...
]
I'm trying to find filter the bookmakers array for each document to only include the objects that match the specified bookmaker values, for example:
{ 'bookmakers.bookmaker': { $in: ['Coral', 'Bet365'] } }
At the moment, I'm using the following mongodb query to only select the bookmakers that are specified, however I need to put the documents back together after they've been seperated by the '$unwind', is there a way I can do this using $group?
await HorseRacingOdds.aggregate([
{ $unwind: "$bookmakers" },
{
$group: {
_id: "$_id",
bookmakers: "$bookmakers"
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
How about a plain $addFields with $filter?
db.collection.aggregate([
{
"$addFields": {
"bookmakers": {
"$filter": {
"input": "$bookmakers",
"as": "b",
"cond": {
"$in": [
"$$b.bookmaker",
[
"Coral",
"Bet365"
]
]
}
}
}
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
Here is the Mongo playground for your reference.

MongoDB Aggregation: How to check if an object containing multiple properties exists in an array

I have an array of objects and I want to check if there is an object that matches multiple properties. I have tried using $in and $and but it does not work the way I want it to.
Here is my current implementation.
I have an array like
"choices": [
{
"name": "choiceA",
"id": 0,
"l": "k"
},
{
"name": "choiceB",
"id": 1,
"l": "j"
},
{
"name": "choiceC",
"id": 2,
"l": "l"
}
]
I am trying to write aggregation code that can check if there is an object that contains both "id":2 and "l":"j" properties. My current implementation checks if there is an object containing the first property then checks if there is an object containing the second one.
How can I get my desired results?
Below, see my aggregation query. The full code is here
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$and: [
{
$in: [
2,
"$choices.id"
]
},
{
$in: [
"j",
"$choices.l"
]
}
]
},
}
}
])
The above query returns true yet there is no object in the array both of the properties id:2 and "l":"J". I know the code works as expected. How can I get my desired results?
You want to use something like $elemMatch
db.collection.find({
choices: {
$elemMatch: {
id: 2,
l: "j"
}
}
})
MongoPlayground
EDIT
In an aggregation $project stage I would use $filter
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$gt: [
{
$size: {
$filter: {
input: "$choices",
as: "choice",
cond: {
$and: [
{
$eq: [
"$$choice.id",
2
]
},
{
$eq: [
"$$choice.l",
"j"
]
}
]
}
}
}
},
0
]
}
}
}
])
MongoPlayground

How to use $elemMatch query in mongodb

I tried to filter the data using $elemmatch but it's not correctly working.
My Scenario
Prod Table
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
},
{
id:3,
name:false
}
]
}
]
Query
db.Prod.find(
{"product.name": true},
{_id: 0, product: {$elemMatch: {name: true}}});
I got Output
[
{
id:1.
product:[
{
id:1,
name:true
}
]
}
]
Excepted Output
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
}
]
}
]
How to achieve this Scenario and I referred this Link Retrieve only the queried element in an object array in MongoDB collection and I tried all the answers in this link mentioned. but Still it's not working can give an example query.
Hmm, you probably didn't try the aggregation provided in referenced link, 'cause it perfectly works.
db.collection.aggregate([
{
$match: {
"product.name": true
}
},
{
$project: {
product: {
$filter: {
input: "$product",
as: "prod",
cond: {
$eq: [
"$$prod.name",
true
]
}
}
}
}
}
])
Will output :
[
{
"_id": 1,
"product": [
{
"id": 1,
"name": true
},
{
"id": 2,
"name": true
}
]
}
]
Here's the example.
EDIT :
From the doc :
Usage Considerations
Both the $ operator and the $elemMatch operator project the first
matching element from an array based on a condition.
Considering this, you cannot achieve what you need with a find query, but only with an aggregation query.

How to access the fields from arrays of a object in two different collections?

This is locations collection data.
{
_id: "1",
location: "loc1",
sublocations: [
{
_id: 2,
sublocation: "subloc1",
},
{
_id: 3,
sublocation: "subloc2",
}
]
},
{
_id: "4",
location: "loc2",
sublocations: [
{
_id: 5,
sublocation: "subloc1",
},
{
_id: 6,
sublocation: "subloc2",
}
]
}
This is products collection data
{
_id: "1",
product: "product1",
prices: [
{
_id: 2,
sublocationid: 2, //ObjectId of object in sublocations array
price: 500
},
{
_id: 3,
sublocationid: 5, //ObjectId of object in sublocations array
price: 200
}
]
}
Now I need to get the sublocation in product schema in the prices array. Expected result is as below.
{
_id: "1",
product: "product1",
prices: [
{
_id: 2,
sublocationid: 3,
sublocation: "subloc2",
price: 500
},
{
_id: 3,
sublocationid: 5,
sublocation: "subloc1"
price: 200
}
]
}
To achieve it, I did it like in the following way.
First, performing aggregation on locations collection - $unwind the sublocations array and store the $out in the new collection.
Second, perform aggregation on 'products' collection - $unwind the prices, $lookup the sublocationid from the new collection and $group them.
Third, after getting data delete the data of new collection.
Is there any other simplified way? Please let me know if there is any.
If you want to stick with 3.4 version, you can try this query:
db.products.aggregate([
{
$unwind: {
"path": "$prices"
}
},
{
$lookup: {
"from": "locations",
"localField": "prices.sublocationid",
"foreignField": "sublocations._id",
"as": "locations"
}
},
{
$unwind: {
"path": "$locations"
}
},
{
$unwind: {
"path": "$locations.sublocations"
}
},
{
$addFields: {
"keep": {
"$eq": [
"$prices.sublocationid",
"$locations.sublocations._id"
]
}
}
},
{
$match: {
"keep": true
}
},
{
$addFields: {
"price": {
"_id": "$prices._id",
"sublocationid": "$prices.sublocationid",
"sublocation": "$locations.sublocations.sublocation",
"price": "$prices.price"
}
}
},
{
$group: {
"_id": "$_id",
"product": { "$first": "$product" },
"prices": { "$addToSet": "$price" }
}
}
]);
It's not as nice as 3.6 version though, because of a higher memory consumption.
You can try below aggregation query in 3.6 version.
Since both local field and foreign field are array you have to $unwind both to do equality comparison.
For this you will have to use new $lookup syntax.
$match with $expr provides comparsion between document fields to look up the location's sublocation document for each product's sublocation id.
$project to project the matching sublocation doc.
$addFields with $arrayElemAt to convert the looked up sublocation array into a document.
$group to push all prices with matching sublocation's document for each product.
db.products.aggregate[
{
"$unwind": "$prices"
},
{
"$lookup": {
"from": "locations",
"let": {
"prices": "$prices"
},
"pipeline": [
{
"$unwind": "$sublocations"
},
{
"$match": {
"$expr": [
"$$prices.sublocationid",
"$sublocations._id"
]
}
},
{
"$project": {
"sublocations": 1,
"_id": 0
}
}
],
"as": "prices.sublocations"
}
},
{
"$addFields": {
"prices.sublocations": {
"$arrayElemAt": [
"$prices.sublocations",
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"product": {
"$first": "$product"
},
"prices": {
"$push": "$prices"
}
}
}
])

Mongodb array $push and $pull

I was looking to pull some value from array and simultaneously trying to update it.
userSchema.statics.experience = function (id,xper,delet,callback) {
var update = {
$pull:{
'profile.experience' : delet
},
$push: {
'profile.experience': xper
}
};
this.findByIdAndUpdate(id,update,{ 'new': true},function(err,doc) {
if (err) {
callback(err);
} else if(doc){
callback(null,doc);
}
});
};
i was getting error like:
MongoError: exception: Cannot update 'profile.experience' and 'profile.experience' at the same time
I found this explanation:
The issue is that MongoDB doesn’t allow multiple operations on the
same property in the same update call. This means that the two
operations must happen in two individually atomic operations.
And you can read that posts:
Pull and addtoset at the same time with mongo
multiple mongo update operator in a single statement?
In case you need replace one array value to another, you can use arrayFilters for update.
(at least, present in mongo 4.2.1).
db.your_collection.update(
{ "_id": ObjectId("your_24_byte_length_id") },
{ "$set": { "profile.experience.$[elem]": "new_value" } },
{ "arrayFilters": [ { "elem": { "$eq": "old_value" } } ], "multi": true }
)
This will replace all "old_value" array elements with "new_value".
Starting from MongoDB 4.2
You can try to update the array using an aggregation pipeline.
this.updateOne(
{ _id: id },
[
{
$set: {
"profile.experience": {
$concatArrays: [
{
$filter: {
input: "$profile.experience",
cond: { $ne: ["$$this", delet] },
},
},
[xper],
],
},
},
},
]
);
Following, a mongoplayground doing the work:
https://mongoplayground.net/p/m1C1LnHc0Ge
OBS: With mongo regular update query it is not possible.
Since Mongo 4.2 findAndModify supports aggregation pipeline which will allow atomically moving elements between arrays within the same document. findAndModify also allows you to return the modified document (necessary to see which array elements were actually moved around).
The following includes examples of:
moving all elements from one array onto the end of a different array
"pop" one element of an array and "push" it to another array
To run the examples, you will need the following data:
db.test.insertMany( [
{
"_id": ObjectId("6d792d6a756963792d696441"),
"A": [ "8", "9" ],
"B": [ "7" ]
},
{
"_id": ObjectId("6d792d6a756963792d696442"),
"A": [ "1", "2", "3", "4" ],
"B": [ ]
}
]);
Example 1 - Empty array A by moving it into array B:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696441") },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, "$A" ] } } },
{ $set: { "A": [] } }
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696441"
},
"A": [],
"B": [
"7",
"8",
"9"
]
}
Example 2.a - Pop element from array A and push it onto array B
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"A": {$exists: true, $type: "array", $ne: [] }},
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ { $first: "$A" } ] ] } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696442"
},
"A": [
"2",
"3",
"4"
],
"B": [
"1"
]
}
Example 2.b - Pop element from array A and push it onto array B but in two steps with a temporary placeholder:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: false } },
update: [
{ $set: { "temp": { $first: "$A" } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
// do what you need to do with "temp"
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: true } },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ "$temp" ] ] } } },
{ $unset: "temp" }
],
new: true
});