Create A function Which can do specific Sum on entire table [duplicate] - mongodb

This question already has answers here:
Is it possible to sum 2 fields in MongoDB using the Aggregation framework?
(3 answers)
Closed 5 years ago.
I have a Mongo DB dataset Like this:
{
"_id" : ObjectId("5a8a75cdec129c86c6a2aaca"),
"asset" : "121",
"A" : 6,
"C" : 12,
"B" : 4,
"D" : 8,
"finalAB" : "",
"finalCD" : "",
},
{
"_id" : ObjectId("5a8a75cdec129c86c6a2aaca"),
"asset" : "122",
"A" : 8,
"C" : 14,
"B" : 6,
"D" : 10,
"finalAB" : "",
"finalCD" : "",
}......upto 4k Entry Like this
Now I want to Create MongoDB function Which can do the Entire Table Calculation of "finalAB" & "finalCD"
NOTE: finalAB = A + B and finalCD = C + D.
Any help is appreciated

You can't use update here because you're referring to other fields in your update however you can try to use Aggregation Framework:
db.col.aggregate([
{
$addFields: {
finalAB: { $add: [ "$A", "$B" ] },
finalCD: { $add: [ "$C", "$D" ] },
}
},
{
$out: "col"
}
])
$addFields simply replaces your finalAB and finalCD with new values. $out is a way to redirect aggregation ouput to a new collection. That's how you can replace (update) existing collection.

Use Aggregate Query :
app.get("/getTotal",function(req,res){
YourModel.aggregate([
{
$addFields: {
finalAB: { $add: [ "$A", "$B" ] },
finalCD: { $add: [ "$C", "$D" ] },
}
},
{
$group:{ _id:null,
finalAB:{ $sum: "$finalAB"},
finalCD:{ $sum: "$finalCD"}
}
}
],function(err,result){
res.json(result);
});
});
Use full Link : https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/

Related

MongoDB sorting by date as type String

Can someone help me with the query for sorting an array by date in ascending order?
I have tried the below query but the sorting is not happening as expected,
db.getCollection(xyz).aggregate([{
$match: {
"_id":{$in:[{"a" : "NA","b" : "HXYZ","c" : "12345","d" : "AA"}]}
}
},{
$sort: {'bal.date': 1}
},
{ $project: {
balances: { $slice: ["$bal",2]}
}
}
])
My collection:
/* 1 */
{
"_id" : {
"a" : "NA",
"b" : "HXYZ",
"c" : "12345",
"d" : "AA"
},
"bal" : [
{
"type" : "E",
"date" : "2015-08-02"
},
{
"type" : "E",
"date" : "2015-08-01"
},
{
"type" : "E",
"date" : "2015-07-07"
}
]
}
Please help me what is the problem in the above query.
Thanks in advance.
You are mixing $match with $sort stage
Correct syntax to used aggregation pipeline stages
db.collection.aggregate([
{ "$match": {
"_id": {
"$eq": {
"a": "NA",
"b": "HXYZ",
"c": "12345",
"d": "AA"
}
}
}},
{ "$unwind": "$bal" },
{ "$sort": { "bal.date": 1 }},
{ "$group": {
"_id": "$_id",
"bal": {
"$push": "$bal"
}
}}
])
From the looks of it, you're saving the date as String, the sort() function will sort the dates as Strings which will not give you the order you're expecting.
You can either run a script that will convert the bal.date field to Date() format and then sort() will work automatically, or you can do the converting + sorting server side.

MongoDB How to compare fields in a single document? [duplicate]

This question already has answers here:
MongoDb query condition on comparing 2 fields
(4 answers)
Closed 4 years ago.
Given the collection "example":
{_id: 1, prediction: "H", result: "A"}
{_id: 2, prediction: "H", result: "H"}
{_id: 3, prediction: "A", result: "A"}
What do I need to do to find records where the prediction and result values match? ie. documents 2 and 3?
Finding all the "H" predictions is:
db.example.find( { prediction: "H" } )
But I need to replace the literal "H" with the value in the result field of the same document.
Edit: Sorry, I should have said that I was using Mongo 3.6.
You should be able to do this with an aggregation query, try out the $redact stage:
db.test.aggregate(
[
{ $redact: {
$cond: {
if: { $eq: [ "$prediction", "$result" ] },
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
]
);
This will yield the result:
{ "_id" : 2, "prediction" : "H", "result" : "H" }
{ "_id" : 3, "prediction" : "A", "result" : "A" }
More info on redact can be found here - https://docs.mongodb.com/manual/reference/operator/aggregation/redact/#redact-aggregation
You can use agregation
db.example.aggregate(
[
{
$project:
{
_id: 1,
prediction: 1,
result: 1,
compare: { $eq: [ "$prediction", "$result" ] }
}
},
{
$match:
{
compare: true
}
}
]
)
If you are using 3.6, this will work. refer this
db.getCollection('TEST').find( { $where: function() {
return this.prediction == this.result
} })
Result :
{
"_id" : 2,
"prediction" : "H",
"result" : "H"
}
{
"_id" : 3,
"prediction" : "A",
"result" : "A"
}

MongoDB sort vs aggregate $sort on array index

With a MongoDB collection test containing the following documents:
{ "_id" : 1, "color" : "blue", "items" : [ 1, 2, 0 ] }
{ "_id" : 2, "color" : "red", "items" : [ 0, 3, 4 ] }
if I sort them in reversed order based on the second element in the items array, using
db.test.find().sort({"items.1": -1})
they will be correctly sorted as:
{ "_id" : 2, "color" : "red", "items" : [ 0, 3, 4 ] }
{ "_id" : 1, "color" : "blue", "items" : [ 1, 2, 0 ] }
However, when I attempt to sort them using the aggregate function:
db.test.aggregate([{$sort: {"items.1": -1} }])
They will not sort correctly, even though the query is accepted as valid:
{
"result" : [
{
"_id" : 1,
"color" : "blue",
"items" : [
1,
2,
0
]
},
{
"_id" : 2,
"color" : "red",
"items" : [
0,
3,
4
]
}
],
"ok" : 1
}
Why is this?
The aggregation framework just does not "deal with" arrays in the same way as is applied to .find() queries in general. This is not only true of operations like .sort(), but also with other operators, and namely $slice, though that example is about to get a fix ( more later ).
So it pretty much is impossible to deal with anything using the "dot notation" form with an index of an array position as you have. But there is a way around this.
What you "can" do is basically work out what the "nth" array element actually is as a value, and then return that as a field that can be sorted:
db.test.aggregate([
{ "$unwind": "$items" },
{ "$group": {
"_id": "$_id",
"items": { "$push": "$items" },
"itemsCopy": { "$push": "$items" },
"first": { "$first": "$items" }
}},
{ "$unwind": "$itemsCopy" },
{ "$project": {
"items": 1,
"itemsCopy": 1,
"first": 1,
"seen": { "$eq": [ "$itemsCopy", "$first" ] }
}},
{ "$match": { "seen": false } },
{ "$group": {
"_id": "$_id",
"items": { "$first": "$items" },
"itemsCopy": { "$push": "$itemsCopy" },
"first": { "$first": "$first" },
"second": { "$first": "$itemsCopy" }
}},
{ "$sort": { "second": -1 } }
])
It's a horrible and "iterable" approach where you essentially "step through" each array element by getting the $first match per document from the array after processing with $unwind. Then after $unwind again, you test to see if that array elements are the same as the one(s) already "seen" from the identified array positions.
It's terrible, and worse for the more positions you want to move along, but it does get the result:
{ "_id" : 2, "items" : [ 0, 3, 4 ], "itemsCopy" : [ 3, 4 ], "first" : 0, "second" : 3 }
{ "_id" : 1, "items" : [ 1, 2, 0 ], "itemsCopy" : [ 2, 0 ], "first" : 1, "second" : 2 }
{ "_id" : 3, "items" : [ 2, 1, 5 ], "itemsCopy" : [ 1, 5 ], "first" : 2, "second" : 1 }
Fortunately, upcoming releases of MongoDB ( as currently available in develpment releases ) get a "fix" for this. It may not be the "perfect" fix that you desire, but it does solve the basic problem.
There is a new $slice operator available for the aggregation framework there, and it will return the required element(s) of the array from the indexed positions:
db.test.aggregate([
{ "$project": {
"items": 1,
"slice": { "$slice": [ "$items",1,1 ] }
}},
{ "$sort": { "slice": -1 } }
])
Which produces:
{ "_id" : 2, "items" : [ 0, 3, 4 ], "slice" : [ 3 ] }
{ "_id" : 1, "items" : [ 1, 2, 0 ], "slice" : [ 2 ] }
{ "_id" : 3, "items" : [ 2, 1, 5 ], "slice" : [ 1 ] }
So you can note that as a "slice", the result is still an "array", however the $sort in the aggregation framework has always used the "first position" of the array in order to sort the contents. That means that with a singular value extracted from the indexed position ( just as the long procedure above ) then the result will be sorted as you expect.
The end cases here are that is just how it works. Either live with the sort of operations you need from above to work with a indexed position of the array, or "wait" until a brand new shiny version comes to your rescue with better operators.

Selecting Distinct values from Array in MongoDB

I have a collection name Alpha_Num, It has following structure. I am trying to find out which Alphabet-Numerals pair will appear maximum number of times ?
If we just go with the data below, pair abcd-123 appears twice so as pair efgh-10001, but the second one is not a valid case for me as it appears in same document.
{
"_id" : 12345,
"Alphabet" : "abcd",
"Numerals" : [
"123",
"456",
"2345"
]
}
{
"_id" : 123456,
"Alphabet" : "efgh",
"Numerals" : [
"10001",
"10001",
"1002"
]
}
{
"_id" : 123456567,
"Alphabet" : "abcd",
"Numerals" : [
"123"
]
}
I tried to use aggregation frame work, something like below
db.Alpha_Num.aggregate([
{"$unwind":"$Numerals"},
{"$group":
{"_id":{"Alpha":"$Alphabet","Num":"$Numerals"},
"count":{$sum:1}}
},
{"$sort":{"count":-1}}
])
Problem in this query is it gives pair efgh-10001 twice.
Question : How to select distinct values from array "Numerals" in the above condition ?
Problem solved.
db.Alpha_Num.aggregate([{
"$unwind": "$Numerals"
}, {
"$group": {
_id: {
"_id": "$_id",
"Alpha": "$Alphabet"
},
Num: {
$addToSet: "$Numerals"
}
}
}, {
"$unwind": "$Num"
}, {
"$group": {
_id: {
"Alplha": "$_id.Alpha",
"Num": "$Num"
},
count: {
"$sum": 1
}
}
}])
Grouping using $addToSet and unwinding again did the trick. Got the answer from one of 10gen online course.

Performing case-statement in mongodb aggregation framework

I'm evaluating how well the MongoDB aggregation framework suits our needs as we are currently running on top of SQL Server. I'm having a hard time performing a specific query:
Say I have the following pseudo records (modeled as columns in a sql table and as a full document in a mongodb collection)
{
name: 'A',
timespent: 100,
},
{
name: 'B',
timespent: 200,
},
{
name: 'C',
timespent: 300,
},
{
name: 'D',
timespent: 400,
},
{
name: 'E',
timespent: 500,
}
I want to group the timespent field in to ranges and count the occurrences so I will get e.g. the following pseudo-records:
results{
0-250: 2,
250-450: 2,
450-650: 1
}
Note that these ranges (250, 450 and 650) are dynamic and will likely be altered over time by the user. In SQL we extracted the results with something like this:
select range, COUNT(*) as total from (
select case when Timespent <= 250 then '0-250'
when Timespent <= 450 then '200-450'
else '450-600' end as range
from TestTable) as r
group by r.range
Again, note that this sql is constructed dynamically by our app to fit the specific ranges available at any one time.
I'm struggling to find the appropriate constructs in the mongodb aggregation framework to perform such queries. I can query for the results of a single range by inserting a $match into the pipeline(i.e. getting the result of a single range) but I cannot grok how to extract all the ranges and their counts in a single pipeline query.
what corresponds to the "case" SQL statement in the aggregation framework, is the $cond operator (see manual). $cond statements can be nested to simulate "when-then" and "else", but I have chosen another approach, because it is easier to read (and to generate, see below): I'll use the $concat operator to write the range string, which then serves as grouping key.
So for the given collection:
db.xx.find()
{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 }
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 }
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 }
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 }
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 }
the aggregate (hardcoded) looks like this:
db.xx.aggregate([
{ $project: {
"_id": 0,
"range": {
$concat: [{
$cond: [ { $lte: ["$timespent", 250] }, "range 0-250", "" ]
}, {
$cond: [ { $and: [
{ $gte: ["$timespent", 251] },
{ $lt: ["$timespent", 450] }
] }, "range 251-450", "" ]
}, {
$cond: [ { $and: [
{ $gte: ["$timespent", 451] },
{ $lt: ["$timespent", 650] }
] }, "range 450-650", "" ]
}]
}
}},
{ $group: { _id: "$range", count: { $sum: 1 } } },
{ $sort: { "_id": 1 } },
]);
and the result is:
{
"result" : [
{
"_id" : "range 0-250",
"count" : 2
},
{
"_id" : "range 251-450",
"count" : 2
},
{
"_id" : "range 450-650",
"count" : 1
}
],
"ok" : 1
}
In order to generate the aggregate command, you have to build the "range" projection as a JSON object ( or you could generate a string and then use JSON.parse(string) )
The generator looks like this:
var ranges = [ 0, 250, 450, 650 ];
var rangeProj = {
"$concat": []
};
for (i = 1; i < ranges.length; i++) {
rangeProj.$concat.push({
$cond: {
if: {
$and: [{
$gte: [ "$timespent", ranges[i-1] ]
}, {
$lt: [ "$timespent", ranges[i] ]
}]
},
then: "range " + ranges[i-1] + "-" + ranges[i],
else: ""
}
})
}
db.xx.aggregate([{
$project: { "_id": 0, "range": rangeProj }
}, {
$group: { _id: "$range", count: { $sum: 1 } }
}, {
$sort: { "_id": 1 }
}]);
which will return the same result as above.
Starting from MongoDB 3.4 we can use the $switch operator to perform a multi-switch statement in the $project stage.
The $group pipeline operator group the documents by "range" and return the "count" for each group using the $sum accumulator operator.
db.collection.aggregate(
[
{ "$project": {
"range": {
"$switch": {
"branches": [
{
"case": { "$lte": [ "$timespent", 250 ] },
"then": "0-250"
},
{
"case": {
"$and": [
{ "$gt": [ "$timespent", 250 ] },
{ "$lte": [ "$timespent", 450 ] }
]
},
"then": "251-450"
},
{
"case": {
"$and": [
{ "$gt": [ "$timespent", 450 ] },
{ "$lte": [ "$timespent", 650 ] }
]
},
"then": "451-650"
}
],
"default": "650+"
}
}
}},
{ "$group": {
"_id": "$range",
"count": { "$sum": 1 }
}}
]
)
With the following documents in our collection,
{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 },
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 },
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 },
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 },
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 }
our query yields:
{ "_id" : "451-650", "count" : 1 }
{ "_id" : "251-450", "count" : 2 }
{ "_id" : "0-250", "count" : 2 }
We may want to add a $sort stage to the pipeline sort our document by range but this will only sort the documents in lexicographic order because of the type of "range".