How can I retrieve all the fields when using $elemMatch? - mongodb

Consider the following posts collection:
{
_id: 1,
title: "Title1",
category: "Category1",
comments: [
{
title: "CommentTitle1",
likes: 3
},
{
title: "CommentTitle2",
likes: 4
}
]
}
{
_id: 2,
title: "Title2",
category: "Category2",
comments: [
{
title: "CommentTitle3",
likes: 1
},
{
title: "CommentTitle4",
likes: 4
}
]
}
{
_id: 3,
title: "Title3",
category: "Category2",
comments: [
{
title: "CommentTitle5",
likes: 1
},
{
title: "CommentTitle6",
likes: 3
}
]
}
I want to retrieve all the posts, and if one post has a comment with 4 likes I want to retrieve this comment only under the "comments" array. If I do this:
db.posts.find({}, {comments: { $elemMatch: {likes: 4}}})
...I get this (which is exactly what I want):
{
_id: 1,
comments: [
{
title: "CommentTitle2",
likes: 4
}
]
}
{
_id: 2,
comments: [
{
title: "CommentTitle4",
likes: 4
}
]
}
{
_id: 3
}
But how can I retrieve the remaining fields of the documents without having to declare each of them like below? This way if added more fields to the post document, I wouldn't have to change the find query
db.posts.find({}, {title: 1, category: 1, comments: { $elemMatch: {likes: 4}}})
Thanks

--EDIT--
Sorry for the misread of your question. I think you'll find my response to this question here to be what you are looking for. As people have commented, you cannot project this way in a find, but you can use aggregation to do so:
https://stackoverflow.com/a/21687032/2313887
The rest of the answer stands as useful. So I think I'll leave it here
You must specify all of the fields you want or nothing at all when using projection.
You are asking here essentially that once you choose to alter the output of the document and limit how one field is displayed then can I avoid specifying the behavior. The bottom line is thinking of the projection part of a query argument to find just like SQL SELECT.It behaves in that * or all is the default and after that is a list of fields and maybe some manipulation of the fields format. The only difference is for _id which is always there by default unless specified otherwise by excluding it, i.e { _id: 0 }
Alternately if you want to filter the collection you nee to place your $elemMatch in thequery itself. The usage here in projection is to explicitly limit the returned document to only contain the matching elements in the array.
Alter your query:
db.posts.find(
{ comments: { $elemMatch: {likes: 4}}},
{ title: 1, category: 1, "comments.likes.$": 1 }
)
And to get what you want we use the positional $ operator in the projection portion of the find.
See the documentation for the difference between the two usages:
http://docs.mongodb.org/manual/reference/operator/query/elemMatch/
http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/

This question is pretty old, but I just faced the same issue and I didn't want to use the aggregation pipeline as it was simple query and I only needed to get all fields applying an $elemMatch to one field.
I'm using Mongoose (which was not the original question but it's very frequent these days), and to get exactly what the question said (How can I retrieve all the fields when using $elemMatch?) I made this:
const projection = {};
Object.keys(Model.schema.paths).forEach(key => {
projection[key] = 1;
});
projection.subfield = { $elemMatch: { _id: subfieldId } };
Model.find({}, projection).then((result) => console.log({ result });

Related

Getting Values from Nested Mongo DBRefs

I'm having an issue with retrieving DBRef values present in an array of sub documents. I've had success using $lookup to retrieve the data from a top level DBRef, but can't seem to put it together when the DBRef is nested inside an array.
In this situation, I have a shipment document that looks like this:
{
id: '123',
title: 'Example'
shipmentLotDetails: [
{
lot: DBRef("inventoryLot", ObjectId("12345")),
quantity: 10
},
{
lot: DBRef("inventoryLot", ObjectId("16587")),
quantity: 5
}
]
}
The lot document looks like this:
{
id: '12345',
title: 'Another Example',
cost: 1.50
}
My goal is to retrieve the cost value for each element in shipmentLotDetails so that I can calculate the cost of units in a shipment. That calculation would just be lot.cost * quantity.
I found a post that describes a process similar to what I'm trying to do here: Mongo Lookup with nested DBRefs. However, the solution process is not explained well enough for me to follow and adapt to my situation.
I know that I need to do a $map on shipmentLotDetails and believe it should look something like this:
db.shipment.aggregate([
{
$project: {
shipmentCost: {
$map: {
input: "$shipmentLotDetails",
in: { $arrayElemAt: [{ $objectToArray: "$$this" }, 0]}
}
}
}
}
])
I know I would also need to do a $lookup to actually pull the lot information from the DBRef, but am not sure where to place that.
Any direction on this would be greatly appreciated. Thank you!

Can't remove object in array using Mongoose

This has been extensively covered here, but none of the solutions seems to be working for me. I'm attempting to remove an object from an array using that object's id. Currently, my Schema is:
const scheduleSchema = new Schema({
//unrelated
_id: ObjectId
shifts: [
{
_id: Types.ObjectId,
name: String,
shift_start: Date,
shift_end: Date,
},
],
});
I've tried almost every variation of something like this:
.findOneAndUpdate(
{ _id: req.params.id },
{
$pull: {
shifts: { _id: new Types.ObjectId(req.params.id) },
},
}
);
Database:
Database Format
Within these variations, the usual response I've gotten has been either an empty array or null.
I was able slightly find a way around this and accomplish the deletion by utilizing the main _id of the Schema (instead of the nested one:
.findOneAndUpdate(
{ _id: <main _id> },
{ $pull: { shifts: { _id: new Types.ObjectId(<nested _id>) } } },
{ new: true }
);
But I was hoping to figure out a way to do this by just using the nested _id. Any suggestions?
The problem you are having currently is you are using the same _id.
Using mongo, update method allows three objects: query, update and options.
query object is the object into collection which will be updated.
update is the action to do into the object (add, change value...).
options different options to add.
Then, assuming you have this collection:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
},
{
"_id": 3
}
]
}
]
If you try to look for a document which _id is 2, obviously response will be empty (example).
Then, if none document has been found, none document will be updated.
What happens if we look for a document using shifts._id:2?
This tells mongo "search a document where shifts field has an object with _id equals to 2". This query works ok (example) but be careful, this returns the WHOLE document, not only the array which match the _id.
This not return:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
}
]
}
]
Using this query mongo returns the ENTIRE document where exists a field called shifts that contains an object with an _id with value 2. This also include the whole array.
So, with tat, you know why find object works. Now adding this to an update query you can create the query:
This one to remove all shifts._id which are equal to 2.
db.collection.update({
"shifts._id": 2
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example
Or this one to remove shifts._id if parent _id is equal to 1
db.collection.update({
"_id": 1
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example

MongoDB - Query nested objects in nested array with array of strings filter

So basically I need to filter my data with my own filter, which is array of strings, but problem is, that that exact field is inside nested object in array in DB. so, part of my Schema looks like this:
members: [
{
_id: { type: Schema.Types.ObjectId, ref: "Users" },
profilePicture: { type: String, required: true },
profile: {
firstName: { type: String },
lastName: { type: String },
occupation: { type: String },
gender: { type: String }
}
}
]
and my filter looks like this
gender: ["male","female"]
expected result with this filter is to get a team which has both male users and female users, if it has only male, or only female, it should not give me that team. but everything i've tried was giving me everything what included males and females even tho there were only male members.
what i've tried:
db.teams.find(members: { $elemMatch: { "profile.gender": { $in: gender } } })
This works only when there is one gender specified in the filter, and well, i know it must not work on what i am trying to achieve, but i dont know how to achieve it. any help will be appreciated
Edit: I've tried to do it in this way
db.teams.find({
$and: [
{ members: { $elemMatch: { "profile.gender": gender[0] } } },
{ members: { $elemMatch: { "profile.gender": gender[1] } } }
]
})
and this gives me result only when both filters are specified, however, if there is only one filter(either "male", or "female") it is giving me nothing.
Use $and operator instead of $in.
db.teams.find(members: {$elemMatch: {$and: [{'profile.gender': 'male'}, {'profile.gender': 'female'}]}})
This query works no matter how many elements you want to compare
db.teams.find({$and: [{'members.profile.gender': 'male'}, {'members.profile.gender': 'female'}]})
You need to dynamically generate the query before passing it to find, if you want to cover more than one case.
You can do this with the $all operator that finds docs where an array field contains contains all specified elements:
var gender = ['male', 'female'];
db.teams.find({'members.profile.gender': {$all: gender}});

Mongoose how to use positional operator to pull from double nested array with specific condition, and return new result

Suppose I have the following schema:
{
_id: ObjectId(1),
title: string,
answers: [
{
_id: ObjectId(2),
text: string,
upVotes: [
{
_id: ObjectId(3),
userId: ObjectId(4)
}
]
}
]
}
What I want is pull vote of a specific user from answer upvotes, and return the new update result.
For example, find a question with id 1, and get its specific answer with id 2, then from that answer pull my vote using userId inside upvotes.
I want to do it with a single findOneAndUpdate query
You can even use single $ positional with the $pull operator to update the nested array
db.collection.findOneAndUpdate(
{ "_id": ObjectId(1), "answers._id": ObjectId(2) },
{ "$pull": { "answers.$.upVotes": { "userId": ObjectId(4) }}}
)
I think I understood that you want to do a search in the specific array
db.collection.update(
{
"_id": "507f1f77bcf86cd799439011", // id field
"answers.upVotes._id":"507f1f77bcf86cd799439011" //id array
}
),{
"$set":{"answers.$.upVotes": {userId :"507f1f77bcf86cd799439011"}}},//edit
//use "addToSet" for add

MongoDB, find field into array

I have items like these in my collection
{
user: data,
somestuff: [somedata ...],
terminals: [ {
label: data,
details: [{more content}, {}, ...]
}]
}
I would use 'find' to extract "details" field for a specific terminal 'label'
I know that there is an easy way to get the "terminals" array by:
collection.find({_id: "..."}, {_id:0, terminals: 1})
Wich return
{ terminals: [ {
label: data,
details: [{more content}, {}, ...]
}]
}
I tried
collection.find({ "terminals.label": data }, { _id: 0, "terminals.$.details": 1 })
As edirican has suggested
And it almost work, but it return the same structure than previously except that the terminals list contain only the labeled document
The result I expect is the details list, extracted from terminals
{ details: [{more content}, {}, ...] }
Thanks for your help !
Use positional ($) projection.
http://docs.mongodb.org/manual/reference/operator/projection/positional/
collection.find({ "terminals.label": 2 }, { _id: 0, "terminals.$.details": 1 })