Using MongoDB $pull to delete documents within an Array - mongodb

I have a collection in MongoDB, which is like following:
{
"_id" : "5327010328645530500",
"members" : [
{
"participationCoeff" : 1,
"tweetID" : "5327010328645530500"
},
{
"participationCoeff" : 1,
"tweetID" : "2820402625046999289"
},
{
"participationCoeff" : 0.6666666666666666,
"tweetID" : "6122060484520699114"
},
{
"participationCoeff" : 1,
"tweetID" : "4656669980325872747"
}
]
}
{
"_id" : "2646953848367646922",
"members" : [
{
"participationCoeff" : 1,
"tweetID" : "2646953848367646922"
},
{
"participationCoeff" : 0.75,
"tweetID" : "7750833069621794130"
},
{
"participationCoeff" : 0.5,
"tweetID" : "6271782334664128453"
}
]
}
Basically, collection have clusters, where a cluster has an _id field and a members field. Members field is an array of documents, having following format.
{
"participationCoeff" : 1,
"tweetID" : "5327010328645530500"
}
Now, from time to time, I have to delete these sub-documents in members attribute of cluster document, by matching tweetID.
However, I'm not able to find a query to achieve this effect. Basically, a tweetID will participate in many clusters, and hence will appear in multiple sub-documents of 'members' attribute of various clusters. I want to supply a bulk $pull operation, where I can remove all the sub-documents in all the clusters (i.e. their members attribute) which match on a particular tweetID.
Some help and intuition will be really helpful.

That's exactly what the $pull operator does, so in the shell you could use an update like:
db.clusters.update({},
{$pull: {members: {tweetID: '5327010328645530500'}}},
{multi: true})
Set the multi option so that every document is updated, not just the first one.

This will delete multiple tweets in a single query just pass an array to $in:-
db.clusters.update({},
{$pull: {members: {$in: [ {tweetID: '5327010328645530500'},{"tweetID" : "2820402625046999289"} ] } } },
{multi: true});
The above method doesnt work in mongoose
so for mongoose do:-
db.clusters.update({},
{$pull: {members:[ {tweetID: '5327010328645530500'},{"tweetID" : "2820402625046999289"} ] } });

In the meantime the update method is deprecated. Therefore one possible up-to-date solution to this would be using the updateMany method instead.
db.clusters.updateMany({},
{$pull: {members: {tweetID: '5327010328645530500'}}})

NOTE: Used in "mongoose": "^5.12.13".
As for today June 22nd, 2021, you can use $in and $pull mongodb operators to remove items from an array of documents :
Parent Document :
{
"name": "June Grocery",
"description": "Remember to pick Ephraim's favorit milk",
"createdDate": "2021-06-09T20:17:29.029Z",
"_id": "60c5f64f0041190ad312b419",
"items": [],
"budget": 1500,
"owner": "60a97ea7c4d629866c1d99d1",
}
Documents in Items array :
{
"category": "Fruits",
"bought": false,
"id": "60ada26be8bdbf195887acc1",
"name": "Kiwi",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92dd67ae0934c8dfce126",
"name": "Toilet Paper",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92fe97ae0934c8dfce127",
"name": "Toothpaste",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92ffb7ae0934c8dfce128",
"name": "Mouthwash",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b931fa7ae0934c8dfce12d",
"name": "Body Soap",
"price": 0,
"quantity": 1
},
{
"category": "Fruit",
"bought": false,
"id": "60b9300c7ae0934c8dfce129",
"name": "Banana",
"price": 0,
"quantity": 1
},
{
"category": "Vegetable",
"bought": false,
"id": "60b930347ae0934c8dfce12a",
"name": "Sombe",
"price": 0,
"quantity": 1
},
Query :
MyModel.updateMany(
{ _id: yourDocumentId },
{ $pull: { items: { id: { $in: itemIds } } } },
{ multi: true }
);
Note: ItemIds is an array of ObjectId.
See below :
[
'60ada26be8bdbf195887acc1',
'60b930347ae0934c8dfce12a',
'60b9300c7ae0934c8dfce129'
]

Related

Need a Mongo query to generate a particular result-set with aggregation

I am new to mongodb, I have a requirement and would like to know how to generate custom resultset using Mongo aggregate operator. Any help would be appreciated.
Need to group the collection by "company" and "status" and would need to produce resultset given below.
Collection
[
{
"company": "google",
"status": "active",
"offer": {
"job": "developer",
"salary": 10000.00
},
},
{
"company": "google",
"status": "active",
"offer": {
"job": "designer",
"salary": 500000.00
},
},
{
"company": "amazon",
"status": "inactive",
"offer": {
"job": "designer",
"salary": 500000.00
},
}
]
Expected Result-Set
[
{
"company" : "google",
"report" : [{
"status" : "active",
"totalSalary" : 60000
},
{
"status" : "inactive",
"totalSalary" : 0
}]
},
{
"company" : "amazon",
"report" : [{
"status" : "active",
"totalSalary" : 0
},
{
"status" : "inactive",
"totalSalary" : 500000.00
}]
}
]
You should 100% check the official documentation on aggregates, it's a bit complicated at first but once you get the hang of it they're great. I also recommend you https://mongoplayground.net/, it's a great site for doing this kind of tests.
What you're looking for is something like this
db.collection.aggregate([
{
$group: {
_id: {
company: "$company"
},
report: {
$addToSet: "$offer"
}
}
}
])
You can test it here. You also probably want to rename the resulting _id field that's mandatory in a group aggregate. You can find how to do that here

Mongodb $filter on Embedded Documents, return all collection data? [duplicate]

This question already has answers here:
Include all existing fields and add new fields to document
(6 answers)
Closed 4 years ago.
I am using mongodb and I have a document which return a json like that:
{
"_id": "5ad9a24be78f9d33888d2567",
"tag": [],
"active": 1,
"code": "_CAROT",
"name": [
{
"lang": "uk",
"translation": "carot"
},
{
"lang": "fr",
"translation": "carotte"
}
],
"season": [],
"category": [],
"createdAt": "2018-04-23T07:59:51.261Z",
"updatedAt": "2018-04-23T07:59:51.261Z",
"__v": 0
}
I want to add a filter on the lang, to get only one translation. So I am using aggregate and $filter to do that. This is what I do :
db.products.aggregate(
[ {$match: {'name.lang': "fr"}},
{$project: { name: {$filter: {
input: '$name',
as: 'item',
cond: {$eq: ['$$item.lang', "fr"]}
}}
}}
])
And I get :
{ "_id" : ObjectId("5ad9a24be78f9d33888d2567"), "name" : [ { "lang" : "fr", "translation" : "carotte" } ] }
{ "_id" : ObjectId("5add96fedf3aac3d049196ca"), "name" : [ { "lang" : "fr", "translation" : "tomate" } ] }
However I would like to get the following result :
{
"_id": "5ad9a24be78f9d33888d2567",
"tag": [],
"active": 1,
"code": "_CAROT",
"name": [
{
"lang": "fr",
"translation": "carotte"
}
],
"season": [],
"category": [],
"createdAt": "2018-04-23T07:59:51.261Z",
"updatedAt": "2018-04-23T07:59:51.261Z",
"__v": 0
}
Basically the default result with just the "fr" result on the "name" field.
Is there a way to do it using mongoDB ?
Thanks a lot
You can use the aggregation $unwind to "unwrap" by translation. I means that for each translation value of each document it will create a new document with only this translation value (at the same level, not in a sub document). Then you will have to then filter with $match to only keep the "fr" translations.
Note you will have to copy each field name to have it in the final result.
Example:
db.products.aggregate([
{ $unwind: '$name' }, // scalar product by name
{ $match: { 'name.lang': 'fr' } }, // only keep documents in french
{ $project: { _id: 0, code: 1, 'name.translation': 1 } } // return code + translation (in french)
])

Aggregate array of subdocuments into single document

My document looks like the following (ignore timepoints for this question):
{
"_id": "xyz-800",
"site": "xyz",
"user": 800,
"timepoints": [
{"timepoint": 0, "a": 1500, "b": 700},
{"timepoint": 2, "a": 1000, "b": 200},
{"timepoint": 4, "a": 3500, "b": 1500}
],
"groupings": [
{"type": "MNO", "group": "<10%", "raw": "1"},
{"type": "IJK", "group": "Moderate", "raw": "23"}
]
}
Can I flatten (maybe not the right term) so the groupings are in a single document. I would like the result to look like:
{
"id": "xyz-800",
"site": "xyz",
"user": 800,
"mnoGroup": "<10%",
"mnoRaw": "1",
"ijkGroup": "Moderate",
"ijkRaw": "23"
}
In reality I would like the mnoGroup and mnoRaw attributes to be created no matter if the attribute groupings.type = "MNO" exists or not. Same with the ijk attributes.
You can use $arrayElemAt to read the groupings array by index in the first project stage and $ifNull to project optional values in the final project stage. Litte verbose, but'll see what I can do.
db.groupmore.aggregate({
"$project": {
_id: 1,
site: 1,
user: 1,
mnoGroup: {
$arrayElemAt: ["$groupings", 0]
},
ijkGroup: {
$arrayElemAt: ["$groupings", -1]
}
}
}, {
"$project": {
_id: 1,
site: 1,
user: 1,
mnoGroup: {
$ifNull: ["$mnoGroup.group", "Unspecified"]
},
mnoRaw: {
$ifNull: ["$mnoGroup.raw", "Unspecified"]
},
ijkGroup: {
$ifNull: ["$ijkGroup.group", "Unspecified"]
},
ijkRaw: {
$ifNull: ["$ijkGroup.raw", "Unspecified"]
}
}
})
Sample Output
{ "_id" : "xyz-800", "site" : "xyz", "user" : 800, "mnoGroup" : "<10%", "mnoRaw" : "1", "ijkGroup" : "Moderate", "ijkRaw" : "23" }
{ "_id" : "ert-600", "site" : "ert", "user" : 8600, "mnoGroup" : "Unspecified", "mnoRaw" : "Unspecified", "ijkGroup" : "Unspecified", "ijkRaw" : "Unspecified" }

MongoDb $lookup query with multiple fields from objects array

This question has previously been marked as a duplicate of this question I can with certainty confirm that it is not.
This is not a duplicate of the linked question because the elements in question are not an array but embedded in individual objects of an array as fields. I am fully aware of how the query in the linked question should work, however that scenario is different from mine.
I have a question regarding the $lookup query of MongoDb. My data structure looks as follows:
My "Event" collection contains this single document:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendees": [
{
"type": 1,
"status": 2,
"contact": ObjectId("mongodbobjectidHEX1")
},
{
"type": 7,
"status": 4,
"contact": ObjectId("mongodbobjectidHEX2")
}
]
}
My "Contact" collection contains these documents:
{
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
},
{
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
What I want to do is perform an aggregate query with the $lookup operator on the "Event" collection and get the following result with full "contact" data:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendees": [
{
"type": 1,
"status": 2,
"contact": {
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
}
},
{
"type": 7,
"status": 4,
"contact": {
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
}
]
}
I have done the same with single elements of "Contact" referenced in another document but never when embedded in an array. I am unsure of which pipeline arguments to pass to get the above shown result?
I also want to add a $match query to the pipeline to filter the data, but that is not really part of my question.
Try this one
db.getCollection('Event').aggregate([{ "$unwind": "$attendees" },
{ "$lookup" : { "from" : "Contact", "localField" : "attendees.contact", "foreignField": "_id", "as" : "contactlist" } },
{ "$unwind": "$contactlist" },
{ "$project" :{
"attendees.type" : 1,
"attendees.status" : 1,
"attendees.contact" : "$contactlist",
"name": 1, "_id": 1
}
},
{
"$group" : {
_id : "$_id" ,
"name" : { $first : "$name" },
"attendees" : { $push : "$attendees" }
}
}
])

Combining multiple sub-documents into a new doc in mongo

I am trying to query multiple sub-documents in MongoDB and return as a single doc.
I think the aggregation framework is the way to go, but, can't see to get it exactly right.
Take the following docs:
{
"board_id": "1",
"hosts":
[{
"name": "bob",
"ip": "10.1.2.3"
},
{
"name": "tom",
"ip": "10.1.2.4"
}]
}
{
"board_id": "2",
"hosts":
[{
"name": "mickey",
"ip": "10.2.2.3"
},
{
"name": "mouse",
"ip": "10.2.2.4"
}]
}
{
"board_id": "3",
"hosts":
[{
"name": "pavel",
"ip": "10.3.2.3"
},
{
"name": "kenrick",
"ip": "10.3.2.4"
}]
}
Trying to get a query result like this:
{
"hosts":
[{
"name": "bob",
"ip": "10.1.2.3"
},
{
"name": "tom",
"ip": "10.1.2.4"
},
{
"name": "mickey",
"ip": "10.2.2.3"
},
{
"name": "mouse",
"ip": "10.2.2.4"
},
{
"name": "pavel",
"ip": "10.3.2.3"
},
{
"name": "kenrick",
"ip": "10.3.2.4"
}]
}
I've tried this:
db.collection.aggregate([ { $unwind: '$hosts' }, { $project : { name: 1, hosts: 1, _id: 0 }} ])
But it's not quite what I want.
You can definitely do this with aggregate. Let's assume your data is in collection named board, so please replace it with whatever your collection name is.
db.board.aggregate([
{$unwind:"$hosts"},
{$group:{_id:null, hosts:{$addToSet:"$hosts"}}},
{$project:{_id:0, hosts:1}}
]).pretty()
it will return
{
"hosts" : [
{
"name" : "kenrick",
"ip" : "10.3.2.4"
},
{
"name" : "pavel",
"ip" : "10.3.2.3"
},
{
"name" : "mouse",
"ip" : "10.2.2.4"
},
{
"name" : "mickey",
"ip" : "10.2.2.3"
},
{
"name" : "tom",
"ip" : "10.1.2.4"
},
{
"name" : "bob",
"ip" : "10.1.2.3"
}
]
}
So your basic problem here is that the arrays are contained in separate documents. So while you are correct to $unwind the array for processing, in order to bring the content into a single array you would need to $group the result across documents, and $push the content to the result array:
db.collection.aggregate([
{ "$unwind": "$hosts" },
{ "$group": {
"_id": null,
"hosts": { "$push": "$hosts" }
}}
])
So just as $unwind will "deconstruct" the array elements, the $push accumulator in $group brings "reconstructs" the array. And since there is no other key to "group" on, this brings all the elements into a single array.
Note that a null grouping key is only really practical when the resulting document would not exceed the BSON limit. Otherwise you are better off leaving the individual elements as documents in themselves.
Optionally remove the _id with an additional $project if required.