MongoDb aggregation - check if values in array are present in collection - mongodb

I'm pretty new to MongoDb. I have a collection of some products, some of them contains an array of ids of other products in the same collection:
[
{
"id": 1,
"relatedProducts": [
"1", "2"
]
},
{
"id": 2,
"relatedProducts": [
"4", "5"
]
}
]
Problem is, not all of the products that are in that relatedProducts array are available in that collection.
I have to create an aggregation that will modify those arrays so only id of available products are present in it. So, for example, if product of id = 5 is not present in that collection, relatedProducts of object with id 2 above will have only one entry in the array ("4").

I recommend you first fetch all the product _id's, then update the documents accordingly.
Here's how I would do this:
const ids = await db.collection.distinct('id');
await db.collection.updateMany(
{
relatedProducts: {$nin: ids}
},
[
{
$set: {
relatedProducts: {
$setIntersection: [{$ifNull: ["$relatedProducts", []]}, ids]
}
}
}
]
)

Related

MongoDB delete embedded documents through array of Ids

I am working on a Node.js application that is using a MongoDB database with Mongoose. I've been stuck in this thing and didn't come up with the right query.
Problem:
There is a collection named chats which contain embedded documents (rooms) as an array of objects. I want to delete these embedded documents (rooms) through Ids which are in the array.
{
"_id": "ObjectId(6138e2b55c175846ec1e38c5)",
"type": "bot",
"rooms": [
{
"_id": "ObjectId(6138e2b55c145846ec1e38c5)",
"genre": "action"
},
{
"_id": "ObjectId(6138e2b545c145846ec1e38c5)",
"genre": "adventure"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "person",
"rooms": [
{
"_id": "ObjectId(6138e2565c145846ec1e38c5)",
"genre": "food"
},
{
"_id": "ObjectId(6138e2b5645c145846ec1e38c5)",
"genre": "sport"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "duo",
"rooms": [
{
"_id": "ObjectId(6138e21c145846ec1e38c5)",
"genre": "travel"
},
{
"_id": "ObjectId(6138e35645c145846ec1e38c5)",
"genre": "news"
}
]
}
I am converting my array of ids into MongoDB ObjectId so I can use these ids as match criteria.
const idsRoom = [
'6138e21c145846ec1e38c5',
'6138e2565c145846ec1e38c5',
'6138e2b545c145846ec1e38c5',
];
const objectIdArray = idsRoom.map((s) => mongoose.Types.ObjectId(s));
and using this query for the chat collection. But it is deleting the whole document and I only want to delete the rooms embedded document because the ids array is only for the embedded documents.
Chat.deleteMany({ 'rooms._id': objectIdArray }, function (err) {
console.log('Delete successfully')
})
I really appreciate your help on this issue.
You have to use $pull operator in a update query like this:
This query look for documents where exists the _id into rooms array and use $pull to remove the object from the array.
yourModel.updateMany({
"rooms._id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
},
{
"$pull": {
"rooms": {
"_id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
}
}
})
Example here.
Also you can run your query without the query parameter (in update queries the first object is the query) like this and result is the same. But is better to indicate mongo the documents using this first object.

Only output embedded documents that exist in other document mongoDB

I have written an aggregation pipeline that up to now returns 2 documents (after matching, projecting and grouping) that look something like this:
{"_id": "group1",
"items": [
{
...item1 fields...
},
{
...item2 fields...
},
....
]
},
{"_id": "group2",
"items": [
{
...item1 fields...
},
{
...item3 fields...
},
....
]
}
The results always have these 2 groups and both groups have an array with items. I want to extend this pipeline in a way so that I only get the items that are common in the two arrays. For the example above, only item1 would be returned.
Any ideas?

Mongodb Match value of array object by Query Method? [duplicate]

I wasn't quite sure how to title this!
So I have an array of product IDs
var productIds = [139,72,73,1,6]
And a bunch of customer documents in MongoDB
{
name: 'James',
products: [ 73, 139 ],
_id: 5741cff3f08e992598d0a39b
}
{
name: 'John',
products: [ 72, 99 ],
_id: 5741d047f08e992598d0a39e
}
I would like to find customers when all of their products appear in the array of product IDs (productIds)
I tried:
'products' : {
'$in': productIds
}
But that returns John, even though 99 doesn't exist in the list of product IDs
I also tried:
'products' : {
'$all': productIds
}
Which returns nothing because none of the customer have ALL the products
Is there a way to achieve what I need in a single query or am I going to have to do some post query processing?
I also tried
'products': {
'$in': productIds,
'$not': {
'$nin': productIds
}
}
but this also seems to return customers when not all product IDs match
You can do this using the .aggregate() method and the $redact operator. In your $cond expressions you need to use the $setIsSubset in order to check if all the elements in the "products" array are in "productIds". This is because you cannot use $in in the conditional expression
var productIds = [139,72,73,1,6];
db.customers.aggregate([
{ "$redact": {
"$cond": [
{ "$setIsSubset": [ "$products", productIds ] },
"$$KEEP",
"$$PRUNE"
]
}}
])

Querying arrays into arrays on MongoDB [duplicate]

I wasn't quite sure how to title this!
So I have an array of product IDs
var productIds = [139,72,73,1,6]
And a bunch of customer documents in MongoDB
{
name: 'James',
products: [ 73, 139 ],
_id: 5741cff3f08e992598d0a39b
}
{
name: 'John',
products: [ 72, 99 ],
_id: 5741d047f08e992598d0a39e
}
I would like to find customers when all of their products appear in the array of product IDs (productIds)
I tried:
'products' : {
'$in': productIds
}
But that returns John, even though 99 doesn't exist in the list of product IDs
I also tried:
'products' : {
'$all': productIds
}
Which returns nothing because none of the customer have ALL the products
Is there a way to achieve what I need in a single query or am I going to have to do some post query processing?
I also tried
'products': {
'$in': productIds,
'$not': {
'$nin': productIds
}
}
but this also seems to return customers when not all product IDs match
You can do this using the .aggregate() method and the $redact operator. In your $cond expressions you need to use the $setIsSubset in order to check if all the elements in the "products" array are in "productIds". This is because you cannot use $in in the conditional expression
var productIds = [139,72,73,1,6];
db.customers.aggregate([
{ "$redact": {
"$cond": [
{ "$setIsSubset": [ "$products", productIds ] },
"$$KEEP",
"$$PRUNE"
]
}}
])

query 2-level array in mongodb

in MongoDB, I have many documents in 2-level array as below:
{
_id:1,
"toPerson": [
[
{
"userid": "test1"
},
{
"userid": "test2"
}
],
[
{
"userid": "test10"
},
{
"userid": "test11"
}
]
]
}
.....
{
_id:99,
"toPerson": [
[
{
"userid": "test2"
},
{
"userid": "test3"
}
],
[
{
"userid": "test100"
},
{
"userid": "test101"
}
]
]
}
Question is how to query all documents that have userid say test2 ?
Have tried:
col.find({'toPerson.userid':'test2'})
it's return nothing. also I have tried using aggregate but found maybe it's not the right direction.
Anyone can help with this?
UPDATE 1
Just read this post
Retrieve only the queried element in an object array in MongoDB collection
but it's different
Structure different: is {field:[ [{ }], [{ }], .... ]}, not { field:[ {}, {} ] }
I want to keep all returned documents structure untouched, $unwind(make toPerson to be 1-level array) or $$PRUNE(remove some fields) will change the structure returned.
UPDATE 2
What I want is to get following result in ONE statement:
col.find({ 'toPerson.0.userid':'test2' })
+ col.find({ 'toPerson.1.userid':'test2' })
+ ... ...
Is there any precise counterpart statement of above results combined together ?
You can query nested arrays like this using two levels of $elemMatch:
db.test.find({toPerson: {$elemMatch: {$elemMatch: {userid: 'test2'}}}})
The outer $elemMatch says match an array element of toPerson where the value passes the inner array $elemMatch test of an element matching {userid: 'test'}.