If I want to sum up numerical values in MongoDB I do it like this:
totalOpenBalance: {
$sum: "$openBalance"
} // sum up all "openBalance" values
But what I'm wondering is, what operator do I use when I want to sum up instances of something? Say if I have a property such as customer_id, and data that looks like this:
{
"customer_id" : 445,
"other_prop" : value
},
{
"customer_id" : 446,
"other_prop" : value
},
Note that I don't want to sum up the values assigned to "customer_id", but rather tally up how many instances of "customer_id" are in the collection of data. In other words, according to the data above, I should get "2" as my output. What operator do I use to do that?
To clarify, this is a step I need to add to an aggregation pipeline I'm using to generate a mongo view.
Any of the below should get you going:
Simple find:
db.collection.find({
"customer_id": { $exists: true }
}).count()
Aggregate with $count:
db.collection.aggregate({
$match: {
"customer_id": { $exists: true }
}
}, {
$count: "numberOfInstances"
})
Aggregate with $group:
db.collection.aggregate({
$match: {
"customer_id": { $exists: true }
}
}, {
$group: {
_id: null,
"numberOfInstances": { $sum: 1 } // count instances
}
})
You can simply use find and $exists and then count the returned rows
db.collection.find( { customer_id: { $exists: true } } ).count()
Or if you want to use aggregate (which I don't think you should do for such simple task) this is how you can do it.
db.collection.aggregate({
$match: {
"customer_id": {
$exists: true
}
}
}, {
$group: {
_id: null,
"total": {
$sum: 1
}
}
})
Here total property will give you the number of instances containing customer_id in them
Related
I am writing a query to do aggregation with respect to one field, however, I want to exclude this aggregation for one value of that field
let coAuthorCutThreshold =100
network.aggregate([
{ $group: { _id: '$title', title :{$ne: "class notes"}, count: { $sum: 1 } }} ,
{ $match: { count: { $gt: coAuthorCutThreshold } } },
{ $sort: { count: -1 } }
]).forEach(function (obj) {
cutTitles.push(obj._id)
});
I want to do aggregation with respect to title but do to not want to do aggregation where the title is "class notes". I tried many command but did not work
I want to include all those values in cutTitles except where title is "class notes"
Match in order to filter out the documents you don't want to group before the group stage.
let coAuthorCutThreshold = 100
network.aggregate([
{ $match: { title: { $ne: "class notes" }}},
{ $group: { _id: '$title', count: { $sum: 1 }}},
{ $match: { count: { $gt: coAuthorCutThreshold }}},
{ $sort: { count: -1 }}
]).forEach(function (obj) {
cutTitles.push(obj._id)
});
I have collection like this:
id:..
Category:Car
Type:BMW
id:..
Category:Car
Type:Ferrari
Color:Red
id..
Category:Bikes
Type:Mountain Bike
id..
Category:Bikes
Type:BMX
I want to make an aggregate and a grouping to have a result like this:
[{Category:Car Type:[BMW,Ferrari] Color:[Red]},
{Category:Bikes Type:[Mountain Bike,BMX]}]
So basically the main field is our Category, and every document keys which is part of that category are sorted out and their values grouped.
Is it possible to achieve this with aggregate?
I tried to make it with $objectToArray but it's not even close to what I want:
db.collection.aggregate([
{
$project: {
data: {
$objectToArray: "$$ROOT"
}
}
},
{
$project: {
data: "$data.k",
value: "$data.v"
}
},
{
$unwind: "$data",
},
{
$unwind: "$value",
},
{
$group: {
_id: null,
keys: {
$addToSet: "$data"
},
values: {
$addToSet: "$value"
}
}
}
])
You're on the right track there.
When converting the entire object to an array, also save the Category field.
The stages needed:
$project to save the Category and convert the object to an array
$unwind the array to consider each field separately
$match to remove _id, Category and any other fields you don't want grouped from the array
$group by Category and k to push the values of each key into an array
$group by Category to collect the keys and arrays together
$project to convert the array with collected values to an object
$addFields to inject the Category into the new object
$replaceRoot to promote the new object
db.collection.aggregate([
{$project: {
Category: 1,
fields: {$objectToArray: "$$ROOT"}
}},
{$unwind: "$fields"},
{$match: {"fields.k": {$not: {$in: ["_id","Category"]}} }},
{$group: {
_id: {
Category: "$Category",
key: "$fields.k"
},
value: {$push: "$fields.v"}
}},
{$group: {
_id: "$_id.Category",
fields: {
$push: {
"k": "$_id.key",
"v": "$value"
}
}
}},
{$project: {
fields: {$arrayToObject: "$fields"}
}},
{$addFields: {
"fields.Category": "$_id"
}},
{$replaceRoot: { newRoot: "$fields"}}
])
Playground
I am looking for a query for a $match stage in my aggregation which do almost the same, as in this question, but..
if field (named rank in my case) doesn't exists in document, add document to results
but if field, exists, apply $operator condition (in my case it's $max) to this field, and add all documents that suits this condition to the results.
MongoPlayground with example collection.
Result should be like this:
[
{
"method": 3,
"item": 1,
"rank": 3 //because it has field named rank, and suits condition {rank: $max}
},
{
"method": 4,
"item": 1 //we need this, because document doesn't have rank field at all
},
{
"method": 5,
"item": 1 //we need this, because document doesn't have rank field at all
}
]
Things, that I have tried already:
{
$match: {
$or: [
{item: id, rank: {$exists: true, $max: "$rank"}}, //id === 1
{item: id, rank: {$exists: false}} //id === 1
]
}
}
UPD: As for now, probably I don't limit with $match stage only, $project is also relevant after default match, so I could request every document during $match stage by id no matter, have the doc rank field or not, and then, during $project stage do a "separation" by rank $exists
Try this one:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$group: {
_id: "$item", //<- Change here your searching field
max: {
$max: "$rank" //<- Change here your field to apply $max
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$type: "$data.rank"
},
"missing"
]
},
{
$eq: [
"$data.rank",
"$max"
]
}
]
}
}
},
{
$replaceWith: "$data"
}
])
MongoPlayground
I have found an answer, separated from #Valijon's method, but it's also based on the logic above. My query is:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$project: {
method: 1,
item: 1,
rank: {
$ifNull: [
"$rank",
0
]
}
}
},
{
$group: {
_id: "$item",
data: {
$addToSet: "$$ROOT"
},
min_value: {
$min: "$rank"
},
max_value: {
$max: "$rank"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$or: [
{
$expr: {
$eq: [
"$data.rank",
"$max_value"
]
}
},
{
$expr: {
$eq: [
"$data.rank",
"$min_value"
]
}
},
]
}
}
])
My query is based on $project stage which gives the empty field value 0. It also could be -1, or any value that isn't used in collection. And then I separate results.
MongoPlayground
I am new to MongoDB and I am stuck in the below scenario.
I have a collection that contains duplicate docs.
I just want to get the sum of the property in each doc excluding the duplicate docs.
My Docs looks like this:
{"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":10,
"transactionId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"uid":"2019-11-20T17:39:17.405Z",
"createdAt":"2019-11-21T08:56:56.589+00:00",
"updatedAt":"2019-11-21T08:56:56.589+00:00","__v":0
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
Currently, I am doing this:
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"}}
},
])
It returns the sum of product_quantity all the matching docs (including the duplicate docs).
Current Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:110}
Expected Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:60}
I want to get the sum of only unique docs. How can I achieve it?
Thanks in advance!
You need to sum inside of the $group _id field, and then use the replaceRoot to achieve the the result you wanted.
MaterialsTrack.aggregate([
{
$match: {
$and: [
{
product_name: "Bottle"
},
{
account_name: "mrfsahas1234"
},
{
action_type: "purchase"
},
{
uid: "2019-11-20T17:39:17.405Z"
}
]
}
},
{
$group: {
_id: {
sellingId: "$sellingId",
PurchaseQuantity: {
$sum: "$product_quantity"
}
}
}
},
{
$replaceRoot: {
newRoot: {
_id: "$_id.sellingId",
PurchaseQuantity: "$_id.PurchaseQuantity"
}
}
}
]);
Sample Output:
[
{
"PurchaseQuantity": 50,
"_id": "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914"
}
]
Playground:
https://mongoplayground.net/p/MOneCRiSlO0
What about adding $addToSet to your aggregations pipeline
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"},"list" : {$addToSet : "$list"}}
},
])
data=[{
locId: '332wn',
locadetails: [
{ loc: 'ny',
status: true
},
{ loc: 'ca',
status: null
},
{ loc: 'tx',
status: null
}
]
}]
I have following query that is trying to find all the locdetails that have open value as null or false
Loc.find({'locId': id}, {'locadetails' : {$elemMatch: {'status': {$ne: true}}}}, (err, locs)=>{
if(err) {
retrun callback(err);
}
callback(null, locs)
});
Problem I have is this query will only return one value o locadetails with null while it should return two as seen in the data.
Please let me know what to do so I can get whole array of items that have status field as null or false ...Thanks
$elemMatch will return first matching element from an array based on a condition. Use Aggregation instead.
Both the $ operator and the $elemMatch operator project the first
matching element from an array based on a condition. Reference
Loc.aggregate([
{ $match: { "locId": "332wn" } },
{ $unwind: "$locadetails" },
{ $match: { "locadetails.status": { $ne: true } } },
{ $group: { _id: "$_id", locId: { $first: "$locId" }, locadetails: { $push: "$locadetails" }, } }
])