Get count of a value of a subdocument inside an array with mongoose - mongodb

I have Collection of documents with id and contact. Contact is an array which contains subdocuments.
I am trying to get the count of contact where isActive = Y. Also need to query the collection based on the id. The entire query can be something like
Select Count(contact.isActive=Y) where _id = '601ad0227b25254647823713'
I am using mongo and mongoose for the first time. Please edit the question if I was not able to explain it properly.

You can use an aggregation pipeline like this:
First $match to get only documents with desired _id.
Then $unwind to get different values inside array.
Match again to get the values which isActive value is Y.
And $group adding one for each document that exists (i.e. counting documents with isActive= Y). The count is stores in field total.
db.collection.aggregate([
{
"$match": {"id": 1}
},
{
"$unwind": "$contact"
},
{
"$match": {"contact.isActive": "Y"}
},
{
"$group": {
"_id": "$id",
"total": {"$sum": 1}
}
}
])
Example here

Related

How to write group by query for embedded array of document in mongodb

I have a collection in below format
{customerID:1,acctDetails:[{accType:"Saving",balance:100},{accType:"checking",balance:500}]}
{customerID:2,acctDetails:[{accType:"Saving",balance:500}]}
I want to find total balance by acctType. I tried below query.
db.<collectionName>.aggregate([{$group:{_id:"$acctDetails.accType",totalBalance:{$sum:"$accDetails.balace"}}}])
But it is not giving right result.
I think that this might solve your problem. You first need to use $unwind to transform each array element in a document, then use $group to sum the total balance by account type.
db.collection.aggregate([
{"$unwind": "$acctDetails"},
{
"$group": {
"_id": "$acctDetails.accType",
"totalBalance": {"$sum": "$acctDetails.balance"}
}
}
])
Working Mongo playground

How to query certain elements of an array of objects? (mongodb)

say I have a mongo DB collection with records as follows:
{
email: "person1#gmail.com",
plans: [
{planName: "plan1", dataValue = 100},
{planName: "plan2", dataValue = 50}
]
},
{
email: "person2#gmail.com",
plans: [
{planName: "plan3", dataValue = 25},
{planName: "plan4", dataValue = 12.5}
]
}
and I want to query such that only the dataValue returns where the email is "person1#gmail.com" and the planName is "plan1". How would I approach this?
You can accomplish this using the Aggregation Pipeline.
The pipeline may look like this:
db.collection.aggregate([
{ $match: { "email" :"person1#gmail.com", "plans.planName": "plan1" }},
{ $unwind: "$plans" },
{ $match: { "plans.planName": "plan1" }},
{ $project: { "_id": 0, "dataValue": "$plans.dataValue" }}
])
The first $match stage will retrieve documents where the email field is equal to person1#gmail.com and any of the elements in the plans array has a planName equal to plan1.
The second $unwind stage will output one document per element in the plans array. The plans field will now be an object containing a single plan object.
In the third $match stage, the unwound documents are further matched against to only include documents with a plans.planName of plan1. Finally, the $project stage excludes the _id field and projects a single dataValue field with a value of plans.dataValue.
Note that with this approach, if the email field is not unique you may have multiple documents consist with just a dataValue field.

How can i search by _id's feature in mongodb

For example, I want to update half of data whose _id is an odd number. such as:
db.col.updateMany({"$where": "this._id is an odd number"})
Instead of integer, _id is mongo's ObejectId which be regard as hexadecimal "string". It is not supported to code as:
db.col.updateMany(
{"$where": "this._id % 2 = 1"},
{"$set": {"a": 1}}
)
so, what is the correct format?
And what if molding according to _id?
This operation can also be done using two database calls.
Get List of _id from collection.
Push only ODD _id into an array.
Update the collection.
Updating the collection:
db.collection.update(
{ _id: { $in: ['id1', 'id2', 'id3'] } }, // Array with odd _id
{ $set: { urkey : urValue } }
)

Accelerate mongo update within two collections

I have a Payments collection with playerId field, which is the _id key of Person collection. I need to count once, what's the maximal payment of a person and save the value to person's document. This is how I do it now:
db.Person.find().forEach( function(person) {
var cursor = db.Payment.aggregate([
{$match: {playerId: person._id}},
{$group: {
_id:"$playerId",
maxp: {$max:"$amount"}
}}
]);
var maxPay = 0;
if (cursor.hasNext()) {
maxPay = cursor.next().maxp;
}
person.maxPay = maxPay;
db.Person.save(person);
});
I suppose seeking maxPay on Payments collection once for all Persons should be faster, but I dunno how to write that in code. Could you help me please?
You can run just a single aggregation pipeline operation which has a $lookup pipeline initially to do a "left join" on the Payment collection. This is necessary in order to get the data from the right collection (payments) embedded within the resulting documents as an array called payments.
The preceding $unwind pipeline deconstructs the embedded payments array i.e. it will generate a new record for each and every element of the payments data field. It basically flattens the data which will be useful for the next $group stage.
In this $group pipeline stage, you calculate your desired aggregates by applying the accumulator expression(s). If for instance your Person schema has other fields you wish to retain, then the $first accumulator operator should suffice in addition to the $max operator for the extra maxPay field.
UPDATE
Unfortunately, there is no operator to "include all fields" in the $group aggregation pipeline operation. This is because the $group pipeline step is mostly used to group and calculate/aggregate data from collection fields (sum, avg, etc.) and returning all the collection's fields is not the pipeline's intended purpose. The group pipeline operator is similar to the SQL's GROUP BY clause where you can't use GROUP BY unless you use any of the aggregation functions (accumulator operators in MongoDB). The same way, if you need to retain most fields, you have to use an aggregation function in MongoDB as well. In this case, you have to apply $first to each field you want to keep.
You can also use the $$ROOT system variable which references the root document. Keep all fields of this document in a field within the $group pipeline, for example:
{
"$group": {
"_id": "$_id",
"maxPay": { "$max": "$payments.amount" },
"doc": { "$first": "$$ROOT" }
}
}
The drawback with this approach is you would need a further $project pipeline to reshape the fields so that they match the original schema because the documents from the resulting pipeline will have only three fields; _id, maxPay and the embedded doc field.
The final pipeline stage, $out, writes the resulting documents of the aggregation pipeline to the same collection, akin to updating the Person collection by atomically replacing the existing collection with the new results collection. The $out operation does not change any indexes that existed on the previous collection. If the aggregation fails, the $out operation makes no changes to the pre-existing collection:
db.Person.aggregate([
{
"$lookup": {
"from": "Payment",
"localField": "_id",
"foreignField": "playerId",
"as": "payments"
}
},
{ "$unwind": {
"path": "$payments",
"preserveNullAndEmptyArrays": true
} },
{
"$group": {
"_id": "$_id",
"maxPay": { "$max": "$payments.amount" },
/* extra fields for demo purposes
"firstName": { "$first": "$firstName" },
"lastName": { "$first": "$lastName" }
*/
}
},
{ "$out": "Person" }
])

MongoDB distinct- returns only matched array elements

I was trying to fetch distinct tags from array for auto-complete module. The collection format is:
{
tags:["apple","mango","apple-pie"]
},
{
tags: ["man","lemon","lemon-lite"]
}
Now, I am interested in getting distinct tags, with prefix q.
The query that I triggered is:
db.portfolio.distinct("tags",{"tags":/app/});
However, this query returned entire array:
["apple","mango","apple-pie"].
My requirement is: ["apple", "apple-pie"].
How can I modify my query to get desired result?
You can do this with aggregation.
You $unwind the tags array.
You $match those tags you are looking for according to the regular expression given.
You $group the tags into a set using $addToSet.
The code looks something like this:
> db.portfolio.aggregate([
{ "$unwind": "$tags" },
{ "$match": { "tags": /app/ }},
{ "$group":
{
"_id": null,
"tags": { "$addToSet": "$tags" }
}
}
]);
{ "_id" : null, "tags" : [ "apple-pie", "apple" ] }
Not possible with distinct because query will return documents containing matching tags(/app/) which mean there will be non matching tags as well. distinct gets a distinct set from all these tags.
So you will have to filter the returning array again( using regex /app/)