Mongodb Sum query returns nothing - javascript

I have this example of activities row collection
{
"_id" : ObjectId("5ec90b5258a37c002509b27d"),
"user_hash" : "asdsc4be9fe7xxx",
"type" : "Expense",
"name" : "Lorem",
"amount" : 10000,
"date_created" : 1590233938
}
I'd like to collect the sum amount of the activity with this aggregate code
db.activities.aggregate(
[
{
$group:
{
_id: "$id",
total: { $sum: "$amount" }
}
},
{
$match: { type: "Expense", "user_hash": "asdsc4be9fe7xxx" }
}
]
)
Expected result : {_id: null, total: xxxxx }
Actual result:
Any solution for this? Thank you in Advance

There're 2 problems with your query:
You making the sum aggregation on each individual document instead doing it on the whole collection because you specify _id: "$id", while you need to specify _id: null.
You're performing the match stage in the aggregating after the group stage. But you need to perform it before because after you group the result will be something like:
{
"_id": null,
"total": 15
}
As you can see this object doesn't have any of the fields that the original objects have therefore 0 results will be matched. The order of stages is important because essentially each stage performs some operation based on the result of the previous stage (there're some exceptions when mongodb automatically optimizes stages but different order in these stages doesn't produce different results).
So the query should be:
db.activities.aggregate(
[
{
$match: { type: "Expense", "user_hash": "asdsc4be9fe7xxx" }
},
{
$group:
{
_id: null,
total: { $sum: "$amount" }
}
},
]
)

Related

How to add within an array information using findOneAndUpdate without deleting information that was previously contained [duplicate]

I am working on an express js application where I need to update a nested array.
1) Schema :
//Creating a mongoose schema
var userSchema = mongoose.Schema({
_id: {type: String, required:true},
name: String,
sensors: [{
sensor_name: {type: String, required:true},
measurements: [{time: String}]
}] });
2)
Here is the code snippet and explanation is below:
router.route('/sensors_update/:_id/:sensor_name/')
.post(function (req, res) {
User.findOneAndUpdate({_id:req.body._id}, {$push: {"sensors" :
{"sensor_name" : req.body.sensor_name , "measurements.0.time": req.body.time } } },
{new:true},function(err, newSensor) {
if (err)
res.send(err);
res.send(newSensor)
}); });
I am able to successfully update a value to the measurements array using the findOneAndUpdate with push technique but I'm failing when I try to add multiple measurements to the sensors array.
Here is current json I get if I get when I post a second measurement to the sensors array :
{
"_id": "Manasa",
"name": "Manasa Sub",
"__v": 0,
"sensors": [
{
"sensor_name": "ras",
"_id": "57da0a4bf3884d1fb2234c74",
"measurements": [
{
"time": "8:00"
}
]
},
{
"sensor_name": "ras",
"_id": "57da0a68f3884d1fb2234c75",
"measurements": [
{
"time": "9:00"
}
]
}]}
But the right format I want is posting multiple measurements with the sensors array like this :
Right JSON format would be :
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
}
],
"measurements" : [
{
"time" : "9:00"
}
]
}],
"__v" : 0 }
Please suggest some ideas regarding this. Thanks in advance.
You might want to rethink your data model. As it is currently, you cannot accomplish what you want. The sensors field refers to an array. In the ideal document format that you have provided, you have a single object inside that array. Then inside that object, you have two fields with the exact same key. In a JSON object, or mongo document in this context, you can't have duplicate keys within the same object.
It's not clear exactly what you're looking for here, but perhaps it would be best to go for something like this:
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
},
{
"time" : "9:00"
}
]
},
{
// next sensor in the sensors array with similar format
"_id": "",
"name": "",
"measurements": []
}],
}
If this is what you want, then you can try this:
User.findOneAndUpdate(
{ _id:req.body._id "sensors.sensor_name": req.body.sensor_name },
{ $push: { "sensors.0.measurements": { "time": req.body.time } } }
);
And as a side note, if you're only ever going to store a single string in each object in the measurements array, you might want to just store the actual values instead of the whole object { time: "value" }. You might find the data easier to handle this way.
Instead of hardcoding the index of the array it is possible to use identifier and positional operator $.
Example:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74")}]
);
You may notice than instead of getting a first element of the array I specified which element of the sensors array I would like to update by providing its ObjectId.
Note that arrayFilters are passed as the third argument to the update query as an option.
You could now make "outer._id" dynamic by passing the ObjectId of the sensor like so: {"outer._id": req.body.sensorId}
In general, with the use of identifier, you can get to even deeper nested array elements by following the same procedure and adding more filters.
If there was a third level nesting you could then do something like:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements.$[inner].example": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74"), {"inner._id": ObjectId("57da0a4bf3884d1fb2234c74"}}]
);
You can find more details here in the answer written by Neil Lunn.
refer ::: positional-all
--- conditions :: { other_conditions, 'array1.array2.field_to_be_checked': 'value' }
--- updateData ::: { $push : { 'array1.$[].array2.$[].array3' : 'value_to_be_pushed' } }

Parsing Exception error when using Terms in ElasticSearch

I'm getting an error on this elastic search for terms. The error message is
"[parsing_exception] [terms] unknown token [START_ARRAY] after [activeIds], with { line=1 & col=63 }"
Active Ids is an array of unique ids. It sort of looks like
const activeIds = [ '157621a1-d892-4f4b-80ca-14feddb837a0',
'd04c5c93-a22c-48c3-a3b0-c79a61bdd923',
'296d40d9-f316-4560-bbc9-001d6f46858b',
'2f8c6c37-588d-4d24-9e69-34b6dd7366c2',
'ba0508dd-0e76-4be8-8b6e-9e938ab4abed',
'ab076ed9-1dd5-4987-8842-15f1b995bc0d',
'ea6b0cff-a64f-4ce3-844e-b36d9f161e6f' ]
let items = await es.search({
"index": table,
"body": {
"from": 0, "size": 25,
"query": {
"terms" : {
"growerId" : {
activeIds
}
},
"bool": {
"must_not": [
{ "match":
{
"active": false
}
},
],
"must": [
{ "query_string" :
{
"query": searchQuery,
"fields": ["item_name"]
}
}
],
}
}
}
})
Appreciate the help!
Edit: Answering this question- "What's the expected result? Can you elaborate and share some sample data? – Nishant Saini 15 hours ago"
I'll try to elaborate a bit.
1) Overall I'm trying to retrieve items that belong to active users. There are 2 tables: user and items. So I'm initially running an ES that returns all the users that contain { active: true } from the user table
2) Running that ES returns an array of ids which I'm calling activeIds. The array looks like what I've already displayed in my example. So this works so far (let me know if you want to see the code for that, but if I'm getting an expected result then I don't think we need that now)
3) Now I want to search through the items table, and retrieve only the items that contain one of the active ids. So an item should look like:
4) expected result is retrieve an array of objects that match the growerId with one of the activeIds. So if I do a search query for "flowers", a single expected result should look like:
[ { _index: 'items-dev',
_type: 'items-dev_type',
_id: 'itemId=fc68dadf-21c8-43c2-98d2-cf574f71f06d',
_score: 11.397207,
_source:
{ itemId: 'fc68dadf-21c8-43c2-98d2-cf574f71f06d',
'#SequenceNumber': '522268700000000025760905838',
item_name: 'Flowers',
grower_name: 'Uhs',
image: '630b5d6e-566f-4d55-9d31-6421eb2cff87.jpg',
dev: true,
growerId: 'd04c5c93-a22c-48c3-a3b0-c79a61bdd923',
sold_out: true,
'#timestamp': '2018-12-20T16:09:38.742599',
quantity_type: 'Pounds',
active: true,
pending_inventory: 4,
initial_quantity: 5,
price: 10,
item_description: 'Field of flowers' } },
So here the growerId matches activeIds[1]
But if I do a search for "invisible", which is created by a an inactive user, I get:
[ { _index: 'items-dev',
_type: 'items-dev_type',
_id: 'itemId=15200473-93e1-477c-a1a7-0b67831f5351',
_score: 1,
_source:
{ itemId: '15200473-93e1-477c-a1a7-0b67831f5351',
'#SequenceNumber': '518241400000000004028805117',
item_name: 'Invisible too',
grower_name: 'Field of Greens',
image: '7f37d364-e768-451d-997f-8bb759343300.jpg',
dev: true,
growerId: 'f25040f4-3b8c-4306-9eb5-8b6c9ac58634',
sold_out: false,
'#timestamp': '2018-12-19T20:47:16.128934',
quantity_type: 'Pounds',
pending_inventory: 5,
initial_quantity: 5,
price: 122,
item_description: 'Add' } },
Now that growerId does not match any of the ids in activeIds.
5) Using the code you helped with, it's returning 0 items.
Let me know if you need more detail. I've been working on this for a bit too long :\
Terms query accept array of terms so the terms query should be defined as below:
"terms": {
"growerId": activeIds
}
You might face other errors as well after making the above correction. So below is full query which might help you:
{
"from": 0,
"size": 25,
"query": {
"bool": {
"must_not": [
{
"match": {
"active": false
}
}
],
"must": [
{
"query_string": {
"query": searchQuery,
"fields": [
"item_name"
]
}
},
{
"terms": {
"growerId": activeIds
}
}
]
}
}
}

Summing and Outputting Totals in Mongo View

In my mongDB backend, I have a view that, after multiple aggregation stages, outputs info that looks like this:
{
"_id" : 25k3ejfjyi32132f9z3,
"customer_id" : 15cgrd582950jj493g5,
"openBalance": 24,
// other data...
},
{
"_id" : 35g6ejfjfj32132f8s4,
"customer_id" : 23gtrd684563jj494f4
"openBalance": 20,
// other data...
}
What I need to do, as a last step, is total up all of the "openBalance" amounts for all records, and output that number in a new field along with the other data. So, in other words, based on the above data, I want to return 44 in the a field titled totalOpenBalance.
Is there a way I can handle this aggregation logic in a mongo view? I'm not sure how to do this, because I'm not wanting to add a field to each record returned, but instead return a value based on the total of the records? It would look something like this:
{
"_id" : 25k3ejfjyi32132f9z3,
"customer_id" : 15cgrd582950jj493g5,
"openBalance": 24,
// other data...
},
{
"_id" : 35g6ejfjfj32132f8s4,
"customer_id" : 23gtrd684563jj494f4
"openBalance": 20,
// other data...
},
"totalOpenBalance": 44
If you add the following code to the end of your pipeline
$group: {
_id: null, // do not really group but throw all documents into the same bucket
documents: { $push: "$$ROOT" }, // push each encountered document into the group
totalOpenBalance: { $sum: "$openBalance" } // sum up all "openBalance" values
}
you will get something that you might be able to use:
{
"_id" : null,
"documents" : [
{
"_id" : 25k3ejfjyi32132f9z3,
"customer_id" : 15cgrd582950jj493g5,
"openBalance" : 24
},
{
"_id" : 35g6ejfjfj32132f8s4,
"customer_id" : 23gtrd684563jj494f4,
"openBalance" : 20
}
],
"totalOpenBalance" : 44
}
If you want to go completely crazy which I would not really recommend then read on. By adding the following stages
{
$group: {
_id: null, // do not really group but throw all documents into the same bucket
documents: { $push: "$$ROOT" }, // push each encountered document into the group
totalOpenBalance: { $sum: "$openBalance" } // sum up all "openBalance" values
}
}, {
$project: {
"_id": 0, // remove the "_id" field
"documents": { $concatArrays: [ "$documents", [ { "totalOpenBalance": "$totalOpenBalance" } ] ] } // append a magic subdocument to the the existing documents
}
}, {
$unwind: "$documents" // just so we can flatten the resulting array into separate documents
}, {
$replaceRoot: {
newRoot: "$documents" // and move the content of our documents field to the root
}
}
you get exactly what you asked for:
{
"_id" : 25k3ejfjyi32132f9z3,
"customer_id" : 15cgrd582950jj493g5,
"openBalance" : 24
},
{
"_id" : 35g6ejfjfj32132f8s4,
"customer_id" : 23gtrd684563jj494f4,
"openBalance" : 20
},
{
"totalOpenBalance" : 44
}
This, however, is probably just an overkill...

Mongo Aggregate: how to compare with a field from another collection?

I am trying to implement a function that collects unread messages from an articles collection. Each article in the collection has a "discussions" entry with discussion comment subdocuments. An example of such a subdocument is:
{
"id": NumberLong(7534),
"user": DBRef("users", ObjectId("...")),
"dt_create": ISODate("2015-01-26T00:10:44Z"),
"content": "The discussion comment content"
}
The parent document has the following (partial) structure:
{
model: {
id: 17676,
title: "Article title",
author: DBRef("users", ObjectId(...)),
// a bunch of other fields here
},
statistics: {
// Statistics will be stored here (pageviews, etc)
},
discussions: [
// Array of discussion subdocuments, like the one above
]
}
Each user also has a last_viewed entry which is a document, an example is as follows:
{
"17676" : "2015-01-10T00:00:00.000Z",
"18038" : "2015-01-10T00:00:00.000Z",
"18242" : "2015-01-20T00:00:00.000Z",
"18325" : "2015-01-20T00:00:00.000Z"
}
This means that the user has looked at discussion comments for the last time on January 10th 2015 for articles with IDs 17676 and 18038, and on January 20th 2015 for articles with IDs 18242 and 18325.
So I want to collect discussion entries from the article documents, and for article with ID 17676, I want to collect the discussion entries that were created after 2015-01-10, and for article with ID 18242, I want to show the discussion entries created after 2015-01-20.
UPDATED
Based on Neil Lunn's reply, the function I have created so far is:
function getUnreadDiscussions(userid) {
user = db.users.findOne({ 'model.id': userid });
last_viewed = [];
for(var i in user.last_viewed) {
last_viewed.push({
'id': parseInt(i),
'dt': user.last_viewed[i]
});
}
result = db.articles.aggregate([
// For now, collect just articles the user has written
{ $match: { 'model.author': DBRef('users', user._id) } },
{ $unwind: '$discussions' },
{ $project: {
'model': '$model',
'discussions': '$discussions',
'last_viewed': {
'$let': {
'vars': { 'last_viewed': last_viewed },
'in': {
'$setDifference': [
{ '$map': {
'input': '$$last_viewed',
'as': 'last_viewed',
'in': {
'$cond': [
{ '$eq': [ '$$last_viewed.id', '$model.id' ] },
'$$last_viewed.dt',
false
]
}
} },
[ false ]
]
}
}
}
}
},
// To get a scalar instead of a 1-element array:
{ $unwind: '$last_viewed' },
// Match only those that were created after last_viewed
{ $match: { 'discussions.dt_create': { $gt: '$last_viewed' } } },
{ $project: {
'model.id': 1,
'model.title': 1,
'discussions': 1,
'last_viewed': 1
} }
]);
return result.toArray();
}
The whole $let thing, and the $unwind after that, transforms the data into the following partial projection (with the last $match commented out):
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7543),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T00:10:44Z"),
"content" : "Some comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
},
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7554),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T02:03:22Z"),
"content" : "Another comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
}
So far so good here. But the problem now is that the $match to select only the discussions created after the last_viewed date is not working. I am getting an empty array response. However, if I hard-code the date and put in $match: { 'discussions.dt_create': { $gt: ISODate("2015-01-20 00:00:00") } }, it works. But I want it to take it from last_viewed.
I found another SO thread where this issue has been resolved by using the $cmp operator.
The final part of the aggregation would be:
[
{ /* $match, $unwind, $project, $unwind as before */ },
{ $project: {
'model': 1,
'discussions': 1,
'last_viewed': 1,
'compare': {
$cmp: [ '$discussions.dt_create', '$last_viewed' ]
}
} },
{ $match: { 'compare': { $gt: 0 } } }
]
The aggregation framework is great, but it takes quite a different approach in problem-solving. Hope this helps anyone!
I'll keep the question unanswered in case anyone else has a better answer/method. If this answer has been upvoted enough times, I'll accept this one.

MongoDB aggregate() .$unwind

I have some data that looks like this (not real data):
{
_id:'cust04',
name:'Diarmuid Rellis',
address:'Elysium, Passage East',
county:'Waterford',
phone:'051-345786',
email:'dreil#drarch.com',
quotations:[
{
_id:'quot03',
supplier_ref:'A2006',
date_received: new Date('2013-05-12T00:00:00'),
date_returned: new Date('2013-05-15T00:00:00'),
supplier_price:35000.00,
customer_price:35000.00,
orders:[
{
_id:'ord03',
order_date: new Date('2013-05-20T00:00:00'),
del_date: new Date('2013-08-12T00:00:00'),
del_address:'Elysium, Passage East, Co. Waterford',
status:'BALPAID'
}
]
},
{
_id:'quot04',
supplier_ref:'A2007',
date_received: new Date('2013-08-10T00:00:00'),
date_returned: new Date('2013-08-12T00:00:00'),
supplier_price:29600.00,
customer_price:29600.00,
orders:[
{
_id:'ord04',
order_date: new Date('2014-03-20T00:00:00'),
del_date: new Date('2014-05-12T00:00:00'),
del_address:'Elysium, Passage East, Co. Waterford',
status:'INPROD'
}
]
}
]
}
I am trying to unwind the quotations and orders arrays, and get a projection of all orders in production which include the customer name, supplier_ref and order date for each.
Here is my query:
db.customers.aggregate([
{ $unwind: "$quotations" },
{ $unwind: "$quotations.orders" },
{ $match: { 'quotations.orders.status': 'INPROD' } },
{
$project: {
name: 1,
supplier_ref: "$quotations.supplier_ref",
order_id: "$quotations.orders._id",
order_date: "$quotations.orders.order_date"
}
},
{
$group: {
_id: "$order_id"
}
}
], function (err, results) {
console.log(results);
})
The query runs successfully, but just gives the order ids, not any of the other fields required. What am I missing ?
EDIT
I am hoping for a result like:
"result": [
{
"_id" : "orderid01",
"name" : "Joe Bloggs",
"supplier_ref" : "A1234",
"date_ordered" : "2012-04-14"
},
{
"_id" : "orderid02",
"name" : "Joe Bloggs",
"supplier_ref" : "A1235",
"date_ordered" : "2012-04-16"
}
]
When I add an extra field to my 'group' function, like so:
$group: {
_id: "$order_id",
supplier_ref: "$supplier_ref"
}
I get the error: "the group aggregate field 'supplier_ref' must be defined as an expression inside an object". Do I have to associate it with the result object in some way ?
Removing the group function altogether produced the results I wanted.

Categories