Mongoose aggregate pipeline not working as expected [duplicate] - mongodb

This question already has an answer here:
Matching ObjectId to String for $graphLookup
(1 answer)
Closed 4 years ago.
I am trying to use the Mongoose aggregate pipeline to query my User collection and Company collection simultaneously.
Specifically, I am trying to return all users and return the user's associated companyName. Each user has a companyId attribute
that maps to a company _id field. Here are my sample user and company documents:
User:
{
_id: ObjectId("5b12ef5ba07ce1f8b212f07b"),
companyId: "12345"
first: "John",
last: "Doe",
}
Company:
{
_id: ObjectId("12345"),
companyName: "UPS"
}
Here is my query code:
User.aggregate([{
$match: {}
},
{
$lookup: {
localField: "companyId",
from: "company",
foreignField: "_id",
as: "companyInfo"
}
},
{
$unwind: "$companyInfo"
},
{
$project: {
first: 1,
last: 1,
"companyInfo.companyName": 1,
}
}
], (err, users) => {
console.log(users) // returns []
});
I THINK the reason is the companyId is stored as a string in my User collection, but it is an objectId in the company collection.
Can anyone confirm? And if it is the issue, I'm not sure how to resolve it... Can someone help? Thanks in advance!

Your assumption is correct.
When doing $lookup, local field and remote field must be of the same type.
There are no way to do a converter when lookup, afaik.
This is the related issue:
https://jira.mongodb.org/browse/SERVER-22781
The version 3.7.3 has it implemented
For now you might want to change your data type

Related

How to update fields in a MongoDB collection if certain conditions met between two collections?

What am I doing?
So I am trying to update two fields in my MongoDB collection. The collection name is mydata and looks like this:
{
id: 123,
name: "John",
class: "A-100",
class_id: "", <-- Need to update this field,
class_type: "", <-- Need to update this field
}
What do I want to do?
I have another collection that is older, but it contains two fields that I need that I do not have in my current collection. But they both have the id field that corresponds. This is how it looks like the other collection:
{
id: 123,
name: "John",
class: "A-100",
class_id: 235, <-- Field That I need,
class_type: "Math" <-- Field That I need
}
What have I done so far?
I started an aggregate function that starts with a $lookup then $unwind then $match then $project. Looks like this:
db.mydata.aggregate([
{
$lookup: {
from: "old_collection",
localField: "id",
foreignField: "id",
as: "newData"
}
},
{
$unwind: "newData"
},
{
$match: {"class": "A-100"}
},
{
$project: {
_id: 0,
"id": "$newData.id",
"class_id": "$newData.class_id",
"class_type": "$newData.class_type"
}
},
Need help here to update mydata collection in the
fields that I pointed in the top
])
In summary
What I am trying to do is: If two objects from different collections have the same Id then pick the keys from the second object and update the keys in the first object.
Is there a way to do that in MongoDB?
Thanks.

$lookup on an array of objects matching by id

So I have two collections, stock and store.
I'm trying to query the store by id.
my stock looks like this
{
_id: ObjectId('5ee93b1e9f96dd1257885748')
productId: 100,
inStore: [
{
_id: ObjectId('5ee7ad659f96dd1257885742'),
store: ObjectId('5ee906e59f96dd1257885745'),
quantity: 10
},
{
_id: ObjectId('5ee9085f9f96dd1257885746'),
store: ObjectId('5ee904549f96dd1257885744'),
quantity: 50
}
]
}
With store being a referenced object to a store
store: { type: Types.ObjectId, ref: 'Store' },
{
_id: ObjectId('5ee904549f96dd1257885744')
name: 'Manchester Store'
}
What's the correct way to query by all stock documents, that contain inStore.store._id?
I'm currently trying this (following How do I query referenced objects in MongoDB?):
const inventories = await InventoryModel()
.aggregate([
{ $unwind: "$inStore" },
{ $lookup: {
from: "Store",
localField: "inStore.store",
foreignField: "_id",
as: "inStore.store"
}},
{$match: {
"inStore.store._id": '5ee906e59f96dd1257885745'
}}
])
But I'm not getting any results? but I do notice that store is an empty array, when i remove the match condition. How do I properly use lookup in this scenario?
Thanks in advance
If you have the store id, you don't need to use the aggregation pipeline at all. Use a simple find with condition
'inStore._id' => ObjectId('...')
Your query condition uses a string, but your ids are ObjectIds. These are different values because their types are different and therefore you won't get any matches.

How to deselect one object in array MongoDB? [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 3 years ago.
In my situation I just need my result but without my objectID in my array.
This is my method :
return Room.findOne(
{
_id: idRoom,
participants: {$elemMatch: {$ne: this.currentUser.profile}},
},
{
'participants.$': 1,
}
)
With elementMatch, the problem is when you found the object, only the first object is returned.
This is my result :
"result": {
"_id": "5da5e77f51e08708b79565e8",
"participants": [
"5da4d5b40cc94f04a7aaad40"
],
And this is the real result I need
"result": {
"_id": "5da5e77f51e08708b79565e8",
"participants": [
"5da4d5b40cc94f04a7aaad40"
"fwnert9248yrhnqwid13982r" // I have another participants
],
And my model is just like this :
const RoomSchema = new Schema({
participants: [{type: Schema.Types.ObjectId,ref: 'Profile'}],
...
}, options)
For others reasons, I can't use aggregate, thank you if you have the solution
So I think you are trying to shape a resultset in mongo with the findOne() method, and any use of the aggregation pipeline framework is out of the question and unavailable for other reasons.
I am not sure this is possible. I believe you will need to perform multiple steps to achieve your desired results. If you can use aggregation pipeline framework here is a pipeline to suit the desired results (I believe)...
db.Room.aggregate(
[
{
"$match": { _id: ObjectId(idRoom)}
},
{
$project: {
"participants": {
$filter: {
input: "$participants",
as: "participant",
cond: {$ne: ["$$participant", this.currentUser.profile] }
}
}
}
}
]
)
...but if you cannot use aggregation pipeline then here is a mongoshell script that accomplishes the task in several steps. The strategy is to capture the whole document by _id then remove the data element from the array then echo the results...
var document = db.Room.findOne( { _id: ObjectId("5da64a62cd63abf99d11f210") } );
document.participants.splice(document.participants.indexOf("5da4d5b40cc94f04a7aaad40"), 1);
document;

How can I perform multiple $lookup's on a query whose results have similar lookup values?

I'm performing a query on a large number of documents from a collection whose schema looks like this:
let mySchema = mongoose.Schema({
title: {
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
company: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Company'
},
....
});
Many of the documents in the above schema will have similar values for user and company and I'm trying to find the most efficient way to perform this query. Here is what I have, but I feel like it isn't as performant as it could be:
this.model('MyModel')
.aggregate([
{$match: match_stuff_here},
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user"
}
$lookup: {
from: "companies",
localField: "company",
foreignField: "_id",
as: "company"
}
{$project: {
_id: 1,
title: 1,
"user._id": 1,
"user.name": 1,
"company._id": 1,
"company.name": 1
}}
])
.exec(function (err, data) {
...
});
It seems like maybe I should somehow group all of the common user ids and company ids together, do a lookup on them as a group, then add the results back in to the original match results. I'm not sure how to do this, or even if that's the right approach. Any ideas on how to optimize this query? Thanks ahead of time!
Since you already have it set up with ObjectId and ref, you can use .populate to make life easier https://mongoosejs.com/docs/populate.html
In your case, this should work for you:
var results = await Model.find(matchQuery).populate('user').populate('company');
console.log(results);
UPDATE
Assuming that you want to pull data from two collections - 'user' and 'company' there is no single query that will retrieve all data needed. You will need atleast 3 calls - match query, get user data, get company data. To my understand, it will map what it needs to populate, then query for the data. You can view the function in source https://github.com/Automattic/mongoose/blob/4.3.7/lib/model.js#L2456
You can also see the queries mongoose uses during the population by enabling debug
mongoose.set('debug', true);
This guy explains it pretty well http://frontendcollisionblog.com/mongodb/2016/01/24/mongoose-populate.html

Find documents matching ObjectIDs in a foreign array

I have a collection Users:
{
_id: "5cds8f8rfdshfd"
name: "Ted"
attending: [ObjectId("2cd9fjdkfsld")]
}
I have another collection Events:
{
_id: "2cd9fjdkfsld"
title: "Some Event Attended"
},
{
_id: "34dshfj29jg"
title: "Some Event NOT Attended"
}
I would like to return a list of all events being attended by a given user. However, I need to do this query from the Events collection as this is part of a larger query.
I have gone through the following questions:
$lookup on ObjectId's in an array - This question has the array as a local field; mine is foreign
MongoDB lookup when foreign field is an array of objects - The array is of objects themselves
MongoDB lookup when foreign field is an array
I have tried various ways of modifying the above answers to fit my situation but have been unsuccessful. The second answer from the third question gets me closest but I would like to filter out unmatching results rather than have them returned with a value of 0.
My desired output:
[
{
_id: "2cd9fjdkfsld"
title: "Some Event Attended"
},
]
One option would be like this:
db.getCollection('Events').aggregate({
$lookup: // join
{
from: "Users", // on Users collection
let: { eId: "$_id" }, // keep a local variable "eId" that points to the currently looked at event's "_id"
pipeline: [{
$match: { // filter where
"_id": ObjectId("5c6efc937ef75175b2b8e7a4"), // a specific user
$expr: { $in: [ "$$eId", "$attending" ] } // attends the event we're looking at
}
}],
as: "users" // push all matched users into the "users" array
}
}, {
$match: { // remove events that the user does not attend
"users": { $ne: [] }
}
})
You could obviously get rid of the users field by adding another projection if needed.