Note: Edits below where I tried this directly using mongo shell and correct collection names, but still the same issue.
I am currently trying to learn Node and Mongodb. I am looking to understand how to add one document with another in a query. All the documentation points back to $lookup.
I have the two following models set up, which both work perfectly on their own
var BearSchema = new Schema({
name: String
});
module.exports = mongoose.model('Bear', BearSchema);
var CommentSchema = new Schema({
creator_id : { type: String, ref: 'Bear' },
comment: String
});
module.exports = mongoose.model('Comment', CommentSchema);
I will omit other set up details and get straight to the queries.
When I run Bear.find() I get the expected result...
[
{
"_id": "585887a29b7915f437742b88",
"name": "new bear",
"__v": 0
}
]
When I run Comment.find() I get the expected result...
[
{
"_id": "585887ae9b7915f437742b89",
"creator_id": "584de876238179030d7d7916",
"comment": "yoyoyo",
"__v": 0
},
{
"_id": "585887e09b7915f437742b8a",
"creator_id": "585887a29b7915f437742b88",
"comment": "ok lets give this a go",
"__v": 0
}
]
Note the creator_id in the second comment is the same as the _id in the bear result.
I then run
Bear.aggregate([
{
$lookup: {
from: "Comment",
localField: "_id",
foreignField: "creator_id",
as: "comments"
}
}
], function (err, bears) {
if (err)
res.send(err);
res.json(bears);
});
and get the following:
[
{
"_id": "585887a29b7915f437742b88",
"name": "new bear",
"__v": 0,
"comments": []
}
]
I was hoping the following would appear:
[
{
"_id": "585887a29b7915f437742b88",
"name": "new bear",
"__v": 0,
"comments": [
{
"_id": "585887e09b7915f437742b8a",
"creator_id": "585887a29b7915f437742b88",
"comment": "ok lets give this a go",
"__v": 0
}
]
}
]
I cant understand in this situation how it would know what "Comment" is referring to.
EDIT: From the documentation I can see the from field says: Specifies the collection in the same database to perform the join with. The from collection cannot be sharded.
EDIT 2: In mongoshell I have ran the following queries and their results, as you can see the same issue is still appearing even with the correct collection name, however I can now see ObjectId() may be the issue...
> show collections
bears
comments
> db.bears.find();
{ "_id" : ObjectId("585887a29b7915f437742b88"), "name" : "new bear", "__v" : 0 }
> db.comments.find();
{ "_id" : ObjectId("585887ae9b7915f437742b89"), "creator_id" : "584de87623817903
0d7d7916", "comment" : "yoyoyo", "__v" : 0 }
{ "_id" : ObjectId("585887e09b7915f437742b8a"), "creator_id" : "585887a29b7915f4
37742b88", "comment" : "ok lets give this a go", "__v" : 0 }
> db.bears.aggregate([ { $lookup: { from: "comments", localField: "_id", foreign
Field: "creator_id", as: "comments" } } ]);
{ "_id" : ObjectId("585887a29b7915f437742b88"), "name" : "new bear", "__v" : 0,
"comments" : [ ] }
whenever you'r using $lookup, you must add an extra "s" in "from" field.
for example:
if your table name is
"register"
then you have to write
"registers"
Note: at the time of $lookup only
I resolved this. There were two issues.
The Bear Schema _id is actually an ObjectID() so it wasnt comparing the two correctly.
I misunderstood what collection names were and so Comment would not have been recognised.
Solution:
When creating the Comment Model I used Schema.ObjectId
var CommentSchema = new Schema({
creator_id : { type: Schema.ObjectId, ref: 'Bear' },
comment: String
});
When doing the query I used comments instead of Comment as this was the collection named Mongoose created.
Bear.aggregate([
{
$lookup: {
from: "comments",
localField: "_id",
foreignField: "creator_id",
as: "comments"
}
}
Related
I am currently trying to retrieve certain objects from a mongoDB database using Mongoose.
For some reason I cant filter by the field "provider" that i assigned to my object, but i can filter by the "date" field.
This works perfectly:
const logs = await ActionLog.find({ date:{ $gt: initialDate, $lt: finishDate}});
if(logs.length == 0){
res.status(204);
res.json({message:"No matches for this search"});
}else
res.send(logs);
but this doesn't, it just brings me all of the elements saved:
const logs = await ActionLog.find({ provider:"example" } );
Some of the elements have the field "provider" and some of them don't, but none of them has provider:"example"
[
{
"_id": "6374768bd302cd09838d4c67",
"module": "event approval",
"date": "2022-11-16T05:35:07.252Z",
"__v": 0
},
{
"_id": "637476aad302cd09838d4c69",
"module": "event update",
"date": "2022-11-16T05:35:38.798Z",
"__v": 0
},
{
"_id": "6374768bd302cd09838d4c11",
"module": "sale",
"provider": "prueba",
"date": "2022-11-20T05:35:07.252Z",
"__v": 0
}
]
The problem was my model didn't have "provider" in it.
Blockquote s "provider" on your mongoose model? I havent use mongoose in a while but I think I recall that for it to query on a field it must actually be on your model. I might be remembering this wrong though. –
Sello Mkantjwa
Once i added "provider" to the model it worked.
I am banging my head against the wall on this...
SEE UPDATE 1 (below) !
I am merging two collections together... I looked at this example ( and ~several~ other examples here on SO ... )
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#lookup-single-equality
I think I am really close, but my expected results are not the same as what I would expect out of the example.
Here is the schema for 'Event'
const EventSchema = new Schema({
name: {type: String, required: true},
})
Here is some 'Event' data
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event"
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event"
}
]
Here is 'MyEvent' schema:
const MyEventSchema = new Schema({
userId: {type: Schema.Types.ObjectId, required: true},
eventId: {type: Schema.Types.ObjectId, required: true},
})
Here is some 'MyEvent' data
[
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"userId": "5e6c2dddad72870c84f8476b",
"eventId": "5e8e4fcf781d96df5c1f5358",
}
]
Here is my code ( the code is wrapped in a promise so it returns resolve and reject with data )
var agg = [
{
$lookup:
{
from: "MyEvent",
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
];
Event.aggregate(agg)
.then( events => {
return resolve(events);
})
.catch(err => {
return reject(null);
})
Here are my results,
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"__v": 0,
"UserIds": []
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"__v": 0,
"UserIds": []
}
]
I expect to see UserIds filled in for event '358 Event', like this
What am I missing ???
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"__v": 0,
"UserIds": [
{"userId": "5e6c2dddad72870c84f8476b"}
]
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"__v": 0,
"UserIds": []
}
]
UPDATE 1
I found a mongo playground and what I have works there, but it doesn't work in my code ??
https://mongoplayground.net/p/fy-GP_yx5j7
In case the link breaks, here is configuration: * select 'bson multiple collections'
db={
"collection": [
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event"
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event"
}
],
"other": [
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"userId": "5e6c2dddad72870c84f8476b",
"eventId": "5e8e4fcf781d96df5c1f5358",
}
]
}
Here is Query:
db.collection.aggregate([
{
$lookup: {
from: "other",
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
])
Here is the result:
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"userIds": [
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"eventId": "5e8e4fcf781d96df5c1f5358",
"userId": "5e6c2dddad72870c84f8476b"
}
]
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"userIds": []
}
]
any suggestions as to why this doesn't work in my code... but works in the playground?
UPDATE 2
I found this:
Need a workaround for lookup of a string to objectID foreignField
UPDATE 3
I have changed the schema to use ObjectId for ids now
still doesn't work
And they are ObjectIds :
RESOLUTION:
So the real answer was a combination of Update 2 and Update 3 and using the right collection name in the lookup.
Update 2 is pretty much my very same question... just using different table names
Update 3 is the correct way to solve this issue.
Mohammed Yousry pointed out the collection name might be wrong... so I looked at my schema and I did have it wrong - changed the name to the right name (along with ObjectId types) and it worked !
It seems there's a typo in from property in $lookup, MyEvent maybe not the collection name
db.collection.aggregate([
{
$lookup: {
from: "MyEvent", // here is the issue I think, check the collection name and make sure that it matches the one you write here
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
])
in mongo playground you attached in the question, if you change the 'other' in the $lookup to anything else, or make a typo in it .. like others instead of other, you will face the same issue
so check that there is no typo in the word MyEvent that you populate from
When I run my http requests in postman, my schema number doesn't show. It shows null.
``const mongoose = require('mongoose');
const OrganisationSchema = mongoose.Schema({
name: String,
yearFounded: Number,
revenue: Number
}, {
timestamps: true
});
module.exports = mongoose.model('Organisations', OrganisationSchema);
{"name": "MadHouse", "yearFounded": "1988", "revenue": "100"}
{
"_id": "5d0d04523668a64609bd20a2",
"name": "Untitled Organisation",
"createdAt": "2019-06-21T16:22:42.974Z",
"updatedAt": "2019-06-21T16:24:29.956Z",
"__v": 0,
"revenue": null,
"yearFounded": null
}
This should be the problem with your insertion or retrieval of data. Just to confirm make revenue and year as required then try inserting.
I just got home and downloaded you project from github, and was able to run it on my machine. And like I said, when the revenue field is sent as a Number, it's works fine.
Here is the JSON on the database:
{
"_id" : ObjectId("5d0d55eb2b177faf109d53e3"),
"name" : "Teste",
"yearFounded" : NumberInt(2019),
"revenue" : 145.8,
"createdAt" : ISODate("2019-06-21T22:10:51.634+0000"),
"updatedAt" : ISODate("2019-06-21T22:10:51.634+0000"),
"__v" : NumberInt(0) }
Try to simulate the same data I sent, and check if you're still getting the same bad behavior.
This is a sample JSON object, among 1000 like them, stored in my MongoDB collection.
{
"_id": ObjectId("5b1bb74ffc7ee601c6915939"),
"groupId": "-abcde",
"applicationId": "avcvcvc",
"integration": "web",
"Category": "message",
"Action": "message",
"Type": "newMessage",
"Id": "activity",
"data": {
"test": "good morning"
},
"timestamp": 1528543055858.0,
"createdAt": ISODate("2018-06-09T11:17:35.868+0000"),
"updatedAt": ISODate("2018-06-09T11:17:35.868+0000"),
"__v": NumberInt(0)
}
This is a query where i fetch data according to date
db.collection.find({"createdAt" : { $gte : new ISODate("2018-06-09T11:17:35.868+0000") }});
This is an operation which i need to perform to the JSON objects recieved from fetching data acc. to date
db.collection.aggregate( [
{ $match: { $or: [ { Type:"on mouse hover click" },{Type:"on mouse out"},
{Type : "on chat start"},{Type :"Load Event"}
] } },
{ $group: { _id:null , count: { $sum: 1 } } }
] );
Is there any way where i can make both these operations perform in a single query rather than fetching data acc. to date first and performing aggregation after? I am new to MongoDB so i cant quite figure out how to do this.
You could just $match for the timestamp during aggregation:
db.collection.aggregate( [
{ $match: { "createdAt" : { $gte : new ISODate("2018-06-09T11:17:35.868+0000") }}},
{ $match: { $or: [ { Type:"on mouse hover click" },{Type:"on mouse out"}, {Type : "on chat start"},{Type :"Load Event"} ] } },
{ $group: { _id:null , count: { $sum: 1 } } }
]);
I am using mongodb and mongoose, And I have a situation where a user creates a rating and a review, what I want is to fetch review of the product whose review has the highest number of votes. Is it possible to do it in mongo?
The structure is
{ "product" : ObjectId("53ccfa53b502542b2f463acd"),
"_id" : ObjectId("53e8675aea39355818ec4ab2"),
"vote" : [ ],
"review" : "Blah Blah Blah",
"stars" : 3,
"email" : "user#example.com", "__v" : 0 }
Now I want to show the review that has achieved the most votes, I know if after find() I put sort() and limit() function, it can be achieved through a field present on the same document level however I do not know how to handle multiple records in this case 'vote'....
The best thing you can do is to maintain a "voteCount" on the document itself. The reasons will become self apparent in a moment.
You can maintain this as members are added or removed from the array. Let's say you are using an ObjectId and the $push and $pull update operators to do this. So you also you $inc with a bit of query logic to make sure you don't duplicate the "User ObjectId" casting the vote. Assuming a model called "Product":
Product.update(
{
"_id": ObjectId("53e8675aea39355818ec4ab2"),
"votes": { "$ne": ObjectId("53e87caaca37ffa384e5a931") }, // the user ObjectId
},
{
"$push": { "votes": ObjectId("53e87caaca37ffa384e5a931" }, // same Id
"$inc": { "voteCount": 1 }
},
function(err) {
}
);
And to remove:
Product.update(
{
"_id": ObjectId("53e8675aea39355818ec4ab2"),
"votes": ObjectId("53e87caaca37ffa384e5a931"), // the user ObjectId
},
{
"$pull": { "votes": ObjectId("53e87caaca37ffa384e5a931" }, // same Id
"$inc": { "voteCount": -1 }
},
function(err) {
}
);
Then it's just a matter of sorting on the field:
Product.find().sort({ "voteCount": -1 }).limit(1).exec(function(err,doc) {
});
But if for some reason you cannot see fit to keep the "voteCount" in the document, then you need to manually "project" this with the aggregation framework. Etiher using the $size aggregate method where you have MongoDB 2.6 or greater:
Product.aggregate(
[
{ "$project": {
"product": 1,
"vote": 1,
"review": 1,
"stars": 1,
"email": 1,
"voteCount": { "$size": "$vote" }
}},
{ "$sort": { "voteCount": -1 } },
{ "$limit": 1 }
],
function(err,result) {
}
);
Or by $unwind on the array and getting the count via $sum for earlier versions:
Product.aggregate(
[
{ "$unwind": "$vote"
{ "$group": {
"_id": "$_id",
"product": { "$first": "$product" },
"vote": { "$push": "$vote" },
"review": { "$first": "$review" },
"stars": { "$first": "$stars" },
"email": { "$first": "$email" },
"voteCount": { "$sum": 1 }
}},
{ "$sort": { "voteCount": -1 } },
{ "$limit": 1 }
],
function(err,result) {
}
);
The aggregation approach really does not make that much sense to implement unless you really need other calculations than the array length. So best just to keep it in the document.
The best way to do so with MongoDB is to add new counter field to explicitly store the number of votes:
{ "product" : ObjectId("53ccfa53b502542b2f463acd"),
"_id" : ObjectId("53e8675aea39355818ec4ab2"),
"vote" : [ {...}, {...}, {...} ],
"vote_count": 3, // <-- new field
"review" : "Blah Blah Blah",
"stars" : 3,
"email" : "user#example.com", "__v" : 0 }
Of course, you have other options, like using Aggregation Pipeline. But adding new field is the best option, because it'll allow you to build an index on this field and do indexed queries.