So I have a collection called Cars that have some fields that are the same, but I want to be able to only get one of the documents based on that field.
[
{
_id:'12345',
model:'Honda'
},
{
_id:'12346',
model:'Honda'
},
{
_id:'12347',
model:'Honda'
},
{
_id:'12348',
model:'Toyota'
},
{
_id:'12349',
model:'Volkswagen'
},
{
_id:'12349',
model:'Volkswagen'
},
]
So here, I want to be able to get the distinct document based on the model field. I just want one document per model field.
first, you want to update mongoose v3 or up
the solution is to use the distinct function
Cars.find().distinct('model', function(error, models) {});
I hope it will help you :)
Use $first to pick a document in $group stage. Then do some wrangling with $unwind and $replaceRoot to retrieve the document.
db.collection.aggregate([
{
$group: {
_id: "$model",
doc: {
$first: "$$ROOT"
}
}
},
{
"$unwind": "$doc"
},
{
"$replaceRoot": {
"newRoot": "$doc"
}
}
])
Here is the Mongo Playground for your reference.
Related
I am working on an app that uses MongoDB (I use Mongoose) as its database.
I have a question, suppose I have this kind of schema:
[{
"user_id":"2328292073"
"username":"Bob",
"subscriptions":[
{
"id":"38271281,
"payments":[
{
"id":"00001",
"amount":"1900"
},
{
"id":"00002",
"amount":"2000"
},
{
"id":"00003",
"amount":"3000"
}
]
}
]
}]
In my case I want to get the payments array for subscription with id = '38271281' of user with id '2328292073', but I just want to retrieve the payment array, nothing else
My query is the following:
Mongoose.findOne({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
"subscriptions.payments": 1
})
But I get the entire document of subscriptions. How can i get the payment array only?
you can try using unwind if you want filteration from db only.
Mongoose.aggregate([
{
'$match': {
'user_id': '2328292093'
}
}, {
'$unwind': {
'path': '$subscriptions'
}
}, {
'$match': {
'subscriptions.id': '38271281'
}
}
])
if you will have multiple documents having same subscription id then you have to group it .
using code level filter function can also be one another approach to do this .
You can try aggregation operators in projection in find method or also use aggregation method,
$reduce to iterate loop of subscriptions and check the condition if id matched then return payment array
db.collection.find({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
payments: {
$reduce: {
input: "$subscriptions",
initialValue: [],
in: {
$cond: [
{ $eq: ["$$this.id", "38271281"] },
"$$this.payments",
"$$value"
]
}
}
}
})
Playground
I have following document fields in a collection
{
_id: ObjectId("5f1b6b26ea1c3b378704b7e9"),
"Gene Name":"xyz"
}
I am trying to add a new field but without spaces. How can I do that ? as the following didn't work
db.getCollection('collection').updateMany(
{ },
{ $set: { "geneName": "$'Gene Name'" } }
)
And also this didn't work either
db.getCollection('collection').find({}).forEach( function(doc)
{
db.getCollection('collection').updateOne(
{_id:doc._id },
{ $set: { "geneName": doc.Gene Name } }
);
} );
Please note I do not want to RENAME the existing field. I simply want to access the current field to add it to a new field.
I will appriciate if you can provide the solution for both forEach and the aggregation pipeline (if either of these are possible for such operation) as I want to learn how to access such annoying fields using "${field}" notation as well
In MongodDB version 4.2+ you can use an aggregation pipeline in the updateOne, updateMany or update collection methods. Here is what you need:
db.collection.update({},
[
{
$set: {
"anotherfield": "$Gene Name"
}
}
])
The result will be:
{
"Gene Name": "xyz",
"_id": ObjectId("5f1b6b26ea1c3b378704b7e9"),
"anotherfield": "xyz"
}
Playground: https://mongoplayground.net/p/-kfY7sqNlBm
I am having trouble with the "$in" operator in Mongoose. At a high level I have a User schema, and one of the fields is an array of a Card schema. Within the Card schema there is a 'score' field. I would like to update the 'score' field based on a list of Card ids. Here is what I am trying to use:
User.updateMany(
{
"_id": userId,
"cards._id": {
"$in": cardIds
}
},
{ $inc: {"cards.$.score": 1 }},
(err) => {console.log(err)}
)
When I run this code, only the first Card in the cardIds array is updated instead of all of them. Any idea why this isn't working? Thanks.
You need to use arrayFilters in .updateMany() , Try this query :
User.updateMany({
"_id": userId
},
{ $inc: { "cards.$[element].score": 1 } },
{ arrayFilters: [{ "element._id": { "$in": cardIds } }] },
(err) => {}
)
I hit an API which follows 50 members' data in a game once a day, and use mongoose to convert the JSON into individual documents in a collection. Between days there is data which is consistent, for example each member's tag (an id for the member in game), but there is data which is different (different scores etc.). Each document has a createdAt property.
I would like to find the most recent document for each member, and thus have an array with each member's tag.
I an currently using the following query to find all documents where tags match, however they are returning all documents, not just one. How do I sort/limit the documents to the most recent one, whilst keep it as one query (or is there a more "mongodb way")?
memberTags = [1,2,3,4,5];
ClanMember.find({
'tag': {
$in: memberTags
}
}).lean().exec(function(err, members) {
res.json(members);
});
Thanks
You can query via the aggregation framework. Your query would involve a pipeline that has stages that process the input documents to give you the desired result. In your case, the pipeline would have a $match phase which acts as a query for the initial filter. $match uses standard MongoDB queries thus you can still query using $in.
The next step would be to sort those filtered documents by the createdAt field. This is done using the $sort operator.
The preceding pipeline stage involves aggregating the ordered documents to return the top document for each group. The $group operator together with the $first accumulator are the operators which make this possible.
Putting this altogether you can run the following aggregate operation to get your desired result:
memberTags = [1,2,3,4,5];
ClanMember.aggregate([
{ "$match": { "tag": { "$in": memberTags } } },
{ "$sort": { "tag": 1, "createdAt: -1 " } },
{
"$group": {
"_id": "$tag",
"createdAt": { "$first": "$createdAt" } /*,
include other necessary fields as appropriate
using the $first operator e.g.
"otherField1": { "$first": "$otherField1" },
"otherField2": { "$first": "$otherField2" },
...
*/
}
}
]).exec(function(err, members) {
res.json(members);
});
Or tweak your current query using find() so that you can sort on two fields, i.e. the tag (ascending) and createdAt (descending) attributes. You can then select the top 5 documents using limit, something like the following:
memberTags = [1,2,3,4,5];
ClanMember.find(
{ 'tag': { $in: memberTags } }, // query
{}, // projection
{ // options
sort: { 'createdAt': -1, 'tag': 1 },
limit: memberTags.length,
skip: 0
}
).lean().exec(function(err, members) {
res.json(members);
});
or
memberTags = [1,2,3,4,5];
ClanMember.find({
'tag': {
$in: memberTags
}
}).sort('-createdAt tag')
.limit(memberTags.length)
.lean()
.exec(function(err, members) {
res.json(members);
});
Ok, so, first, let's use findOne() so you get only one document out of the request
Then to sort by the newest document, you can use .sort({elementYouWantToSort: -1}) (-1 meaning you want to sort from newest to oldest, and 1 from the oldest to the newest)
I would recommend to use this function on the _id, which already includes creation date of the document
Which gives us the following request :
ClanMember.findOne({
'tag': {
$in: memberTags
}
}).sort({_id: -1}).lean().exec(function(err, members) {
res.json(members);
});
I'm trying to add a new field in all documents that contain the sum of an array of numbers.
Here is the Schema (removed irrelevant fields for brevity):
var PollSchema = new Schema(
{
votes: [Number]
}
);
I establish the model:
PollModel = mongoose.model('Poll', PollSchema);
And I use aggregation to create a new field that contains the sum of the votes array.
PollModel.aggregate([
{
$project: {
totalVotes: { $sum: "$votes"}
}
}
]);
When I startup my server, I get no errors; however, the totalVotes field hasn't been created. I used this documentation to help me. It similarly uses the $sum operator and I did it exactly like the documentation illustrates, but no results.
MongoDb aggregation doesn't save its result into database. You just get the result of aggregation inline within a callback.
So after aggregation you would need to do multi update to your database:
PollModel.aggregate([
{
$project: { totalVotes: { $sum: "$votes"} }
}]).exec( function(err, docs){
// bulk is used for updating all records within a single query
var bulk = PollModel.collection.initializeUnorderedBulkOp();
// add all update operations to bulk
docs.forEach(function(doc){
bulk.find({_id: doc._id}).update({$set: {totalVotes: doc.totalVotes}});
});
// execute all bulk operations
bulk.execute(function(err) {
});
})
});
Unfortunately this does not work as you think it does because "votes" is actually an array of values to start with, and then secondly because $sum is an accumulator operator for usage in the $group pipeline stage only.
So in order for you to get the total of the array as another property, first you must $unwind the array and then $group together on the document key to $sum the total of the elements:
PostModel.aggregate(
[
{ "$unwind": "$votes" },
{ "$group": {
"_id": "$_id",
"anotherField": { "$first": "$anotherField" },
"totalVotes": { "$sum": "$votes" }
}}
],
function(err,results) {
}
);
Also noting here another accumulator in $first would be necessary for each additional field you want in results as $group and $project only return the fields you ask for.
Generally though this is better to keep as a property within each document for performance reasons, as it's faster than using aggregate. So to do this just increment a total each time you $push to an array by also using $inc:
PostModel.update(
{ "_id": id },
{
"$push": { "votes": 5 },
"$inc": { "totalVotes": 5 }
},
function(err,numAffected) {
}
);
In that way the "totalVotes" field is always ready to use without the overhead of needing to deconstruct the array and sum the values for each document.
You don't have totalVotes in your schema. Just try the below code.
var PollSchema = new Schema(
{
votes: [Number],
totalVotes: Number
}
);
PollModel.aggregate([
{
$project: {
totalVotes: { $sum: "$votes"}
}
}
]);
or
resultData.toJSON();
#Blakes Seven and #Volodymyr Synytskyi helped me arrive to my solution! I also found this documentation particularly helpful.
PollModel.aggregate(
[
{ '$unwind': '$votes' },
{ '$group': {
'_id': '$_id',
'totalVotes': { '$sum': '$votes' }
}}
],
function(err,results) {
// console.log(results);
results.forEach(function(result){
var conditions = { _id: result._id },
update = { totalVotes: result.totalVotes },
options = { multi: true };
PollModel.update(conditions, update, options, callback);
function callback (err, numAffected) {
if(err) {
console.error(err);
return;
} else {
// console.log(numAffected);
}
}
});
}
);