I have an array and each list item in that array is an object the has a value key. What I would like to do is add all of the values to get a total. I want to do this on the back end rather than the front end. I have tried the aggregate method, but haven't had any luck as it returns an empty array. Here is my array:
"budgets" : [
{
"name" : "check",
"value" : "1000",
"expense" : "false",
"uniqueId" : 0.9880268634296954
},
{
"name" : "car",
"value" : "500",
"expense" : "true",
"uniqueId" : 0.1904486275743693
},
{
"name" : "car2",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.23043920518830419
},
{
"name" : "car23",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.014449386158958077
},
{
"name" : "car235",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.831609656335786
}
],
What I want to do is get the total of the values that have "expense" : "true" and the get a separate total of "expense" : "false" how would I do this? Like I said I have tried the aggregate method, but I must be doing it wrong.
If you are new to using the aggregation framework commands you might not yet be aware of the $cond operator. You can use this in order to get your totals separated:
db.collection.aggregate([
// Unwind the array to de-normalize the items
{ "$unwind": "$budgets" },
// Group the totals with conditions
{ "$group": {
"_id": "$_id",
"expense": { "$sum": {
"$cond": [
"$budgets.expense",
"$budgets.value",
0
]
}},
"nonExpense": { "$sum": {
"$cond": [
"$budgets.expense",
0,
"$budgets.value"
]
}}
}}
])
So what that will do is evaluate the true/false condition of expense as the first argument, and when the condition is actually true then the second argument of the $cond will be chosen and passed on to the $sum. If the condition evaluates to false then the second argument is chosen.
But looking at your data you appear to have a problem:
{
"name" : "check",
"value" : "1000",
"expense" : "false",
"uniqueId" : 0.9880268634296954
},
Noting here that all of the fields, and most notably the expense and value items, are all strings. So this is a problem as while we could get around evaluating the true/false values by doing string comparisons rather than direct boolean, you simply cannot co-erce a string as a number that can be passed to $sum.
So firstly you need to fix your data, unless it is not actually in the form as you have represented. But when it meets a correct form, then you can do your aggregation.
First of all covert you datatype of value from string to integer(summable) type and then use
db.collectioName.aggregate(
{$unwind:"$budgets"},
{
$group:{_id: "$budgets.expense",
total: { $sum: "$budgets.value"}}
})
and then you should get result like this
{
"result" : [
{
"_id" : "true",
"total" : 500
},
{
"_id" : "false",
"total" : 2000
}
],
"ok" : 1
}
Try something like this
db.collection.aggregate([ { "$unwind": "$budgets" },
{ $match: { expense: "true" } },
{ $group: { _id: "$_id",
name: { $addToSet: "$name" },
uniqueId: { $addToSet: "$uniqueId" },
total: { $sum: "$$budgets.value" } } }
])
Related
Any idea why this isn't returing the average (returns null)?
Pattern.aggregate([
{ $match: {
name: 'asdfaddf'
}},
{ $unwind: "$ratings" },
{ $group : { _id: "test", ratings : { $avg : "$ratings.rating" } } }
My document looksl ike this
"ratings" : [
{
"rating" : "5",
"userID" : "5a73ef842aed0f399ff4ee40",
"_id" : ObjectId("5a74893230eed03f1ebf3c5f")
},
{
"rating" : "3",
"userID" : "awefawefawfawef",
"_id" : ObjectId("5a74893230aad03f1ebf3c5e")
}
],
Looks like using non-numeric values, as state in the mongodb documentation these will be ignored:
$avg ignores non-numeric values, including missing values. If all of the operands for the average are non-numeric, $avg returns null since the average of zero values is undefined.
https://docs.mongodb.com/manual/reference/operator/aggregation/avg/#non-numeric-or-missing-values
Here is an example of a nested document that I have in my collection:
"person" : [
{
"title" : "front-end developer",
"skills" : [
{
"name" : "js",
"project" : "1",
},
{
"name" : "CSS",
"project" : "5",
}
]
},
{
"title" : "software engineer",
"skills" : [
{
"name" : "Java",
"project" : "1",
},
{
"name" : "c++",
"project" : "5",
}
]
}
]
Is there a simple way of determining whether other documents are identical to this object e.g. has the same keys, value and array indexes? Currently my method of checking for duplicates is very long and requires multiple nested loops. Any help would be greatly appreciated. Thanks!
If you want to get a list of identical (except for the _id field, obviously) documents in your collection, here is how you can do that:
collection.aggregate({
$project: {
"_id": 1, // keep the _id field where it is anyway
"doc": "$$ROOT" // store the entire document in the "doc" field
}
}, {
$project: {
"doc._id": 0 // remove the _id from the stored document because we do not want to compare it
}
}, {
$group: {
"_id": "$doc", // group by the entire document's contents as in "compare the whole document"
"ids": { $push: "$_id" }, // create an array of all IDs that form this group
"count": { $sum: 1 } // count the number of documents in this group
}
}, {
$match: {
"count": { $gt: 1 } // only show what's duplicated
}
})
As always with the aggregation framework, you can try to make sense of what exactly is going on in each step by commenting out all steps and then activating everything again stage by stage.
I have an array like this:
{
"_id" : ObjectId("581b7d650949a5204e0a6e9b"),
"types" : [
{
"type" : ObjectId("581b7c645057c4602f48627f"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
},
{
"type" : ObjectId("581ca0e75b1e3058521a6d8c"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
}
],
"__v" : 0
},
{
"_id" : ObjectId("581b7d650949a5204e0a6e9c"),
"types" : [
{
"type" : ObjectId("581b7c645057c4602f48627f"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
}
],
"__v" : 0
}
And I want to create a query that will return me the elementswhere the array of types ALL match a $in array.
For example:
query([ObjectId("581b7c645057c4602f48627f"), ObjectId("581ca0e75b1e3058521a6d8c")])
should return elements 1 and 2
query([ObjectId("581b7c645057c4602f48627f")])
should return element 2
query([ObjectId("581ca0e75b1e3058521a6d8c")])
should return nothing
I tried
db.getCollection('elements').find({'types.type': { $in: [ObjectId("581ca0e75b1e3058521a6d8c")]}})
But it returns the elements if only one types matches
You may have to use aggregation as $in and $elematch will return only matching elements. Project stage does set equals to create a all match flag and matches in the last stage with true value.
aggregate([ {
$project: {
_id: 0,
isAllMatch: {$setIsSubset: ["$types.type", [ObjectId("581b7c645057c4602f48627f")]]},
data: "$$ROOT"
}
}, {
$match: {
isAllMatch: true
}
}])
Sample Output
{
"isAllMatch": true,
"data": {
"_id": ObjectId("581b7d650949a5204e0a6e9c"),
"types": [{
"type": ObjectId("581b7c645057c4602f48627f"),
"quantity": 4,
"_id": ObjectId("581b7d650949a5204e0a6e9e")
}],
"__v": 0
}
}
Alternative version:
This version combines both project and match stages into one $redact stage with $cond operator to decide whether to keep or prune the elements.
aggregate([{
"$redact": {
"$cond": [{
$setIsSubset: ["$types.type", [ObjectId("581b7c645057c4602f48627f")]]
},
"$$KEEP",
"$$PRUNE"
]
}
}])
I have the following data structure in mongodb
[
{
"id" : "unique id 1",
"timeStamp" : "timeStamp",
"topicInfo" : [
{
topic : "topic1",
offset : "offset number",
time: 1464875267637
},
{
topic : "topic2",
offset : "offset number",
time: 1464875269709
},
{
topic : "topic3",
offset : "offset number",
time : 1464875270849
}
]
},
{
"id" : "unique id 2",
"timeStamp" : "timeStamp",
"topicInfo" : [
{
topic : "15",
offset : "offset number",
time : 1464875271884
},
{
topic : "topic2",
offset : "offset number",
time : 1464875273887
},
{
topic : "topic3",
offset : "offset number",
time : 1464875272848
}
]
}
]
Now I want to find all the entry That has topic called "topic2" and the value of time is maximum compare to other object's in the "topicInfo" array. I also want to sort them by "timeStamp". From the example code the query should return the second object. I am not able to write the query any help would be much appreciated.
The optimal best way to do this is in MongoDB 3.2 or newer. We need to $project our documents and use the $filter operator to return a subset of the "topicInfo" array that matches our condition. And as of MongoDB3.2 , we can use the $max in the $project stage in the condition expression and perform a logical operation on the returned value.
The final stage in the pipeline is the $match stage where you filter out those documents with empty "topicInfo" using the $exists element query operator and the dot notation to access the first element in the array. This also reduces both the amount of data sent over the wire and the time and memory used to decode documents on the client-side.
db.collection.aggregate([
{ "$project": {
"topicInfo": {
"$filter": {
"input": "$topicInfo",
"as": "t",
"cond": {
"$and": [
{ "$eq": [ "$$t.topic", "topic2"] },
{ "$eq": [ "$$t.time", { "$max": "$topicInfo.time" } ] }
]
}
}
}
}},
{ "$match": { "topicInfo.0": { "$exists": true } } }
])
You can do it with aggregation framework like this:
db.test.aggregate(
{ $unwind: '$topicInfo' },
{ $match: { 'topicInfo.topic': 'topic2' } },
{ $group: {
_id: '$id',
timestamp: { $first: '$timestamp' },
time: { $max: '$topicInfo.time' } }
},
{ $sort: { timestamp: 1 } }).pretty()
This is Collection Structure
[{
"_id" : "....",
"name" : "aaaa",
"level_max_leaves" : [
{
level : "ObjectIdString 1",
max_leaves : 4,
}
]
},
{
"_id" : "....",
"name" : "bbbb",
"level_max_leaves" : [
{
level : "ObjectIdString 2",
max_leaves : 2,
}
]
}]
I need to find the subdocument value of level_max_leaves.level filter when its matching with given input value.
And this how I tried,
For example,
var empLevelId = 'ObjectIdString 1' ;
MyModel.aggregate(
{$unwind: "$level_max_leaves"},
{$match: {"$level_max_leaves.level": empLevelId } },
{$group: { "_id": "$level_max_leaves.level",
"total": { "$sum": "$level_max_leaves.max_leaves" }}},
function (err, res) {
console.log(res);
});
But here the $match filter is not working. I can't find out exact results of ObjectIdString 1
If I filter with name field, its working fine. like this,
{$match: {"$name": "aaaa" } },
But in subdocument level its returns 0.
{$match: {"$level_max_leaves.level": "ObjectIdString 1"} },
My expected result was,
{
"_id" : "ObjectIdString 1",
"total" : 4,
}
You have typed the $match incorrectly. Fields with $ prefixes are either for the implemented operators or for "variable" references to field content. So you just type the field name:
MyModel.aggregate(
[
{ "$match": { "level_max_leaves.level": "ObjectIdString 1" } },
{ "$unwind": "$level_max_leaves" },
{ "$match": { "level_max_leaves.level": "ObjectIdString 1" } },
{ "$group": {
"_id": "$level_max_leaves.level",
"total": { "$sum": "$level_max_leaves.max_leaves" }
}}
],
function (err, res) {
console.log(res);
}
);
Which on the sample you provide produces:
{ "_id" : "ObjectIdString 1", "total" : 4 }
It is also good practice to $match first in your pipeline. That is in fact the only time an index can be used. But not only for that, as without the initial $match statement, your aggregation pipeline would perform an $unwind operation on every document in the collection, whether it met the conditions or not.
So generally what you want to do here is
Match the documents that contain the required elements in the array
Unwind the array of the matching documents
Match the required array content excluding all others