Node.JS + MongoDB aggregation framework average value - node.js

I have been thinking of using Node and MongoDb for a ratings system for an Android app, so far I have implemented Create, Update and Delete fine but am at a loss as to what to do when I want to get the average rating back from the DB. Here is the data structure I gave decided upon:
[
{
"_id": "1",
"ratings": [
{
"value": "5",
"user": "CoolUser1"
},
{
"value": "4",
"user": "CoolUser2"
}
]
},
{
"_id": "2",
"ratings": [
{
"value": "4",
"user": "CoolUser3"
},
{
"value": "2",
"user": "CoolUser4"
}
]
}
]
Basically what I need is the average value in ratings for a given _id.
Here is the Update method I am using to give a sense of how I have setup the mongoDB connection:
exports.updateRating = function(req, res) {
var id = req.params.id;
var rating = req.body;
console.log('Updating a rating: ' + id);
console.log(JSON.stringify(rating));
ratings.update({'_id': id},
{$push: rating},
{upsert:true},
function(err, result) {
if (err) {
console.log('Error updating rating: ' + err);
res.send({'error':'An error has occurred'});
} else {
console.log('' + result + ' document(s) updated');
res.send(rating);
}
}
);
}

Is it critical to store ratings.value as a string?
This will work, but only if you convert rating to number before safe
ratings.aggregate([
{ $match: { _id: id } },
{ $unwind: '$ratings' },
{ $group: { _id: null, avg: { $avg: '$ratings.value' } } }
], function(err, result) {
console.log(result);
db.close();
});

Related

Mongodb aggregation in Nodejs and express js

The below aggregation is working in mongo db, but when i tried to integrate this with node js to get the count value through API, its returned null value
I am currently finding out ways to do Aggregation in Node js.
Below are the integration code with node js
app.get('/count', (req, res) => {
var name = req.params.name;
adpostdata.aggregate([
{ "$facet": {
"Total": [
{ "$match" : { "category": { "$exists": true }}},
{ "$count": "Total" },
]
}},
{ "$project": {
"Total": { "$arrayElemAt": ["$Total.Total", 0] },
}}
], (err, result) => {
if (err) return console.log(err)
console.log(result)
res.send(result+"")
})
})
my input is
{
category :"dress"
}
{
category:"cars"
}
Thanks all of you for your help, now i am able to get my excepted outputs
app.get('/count', (req, res) => {
adpostdata.aggregate(
[
// Count the number of books published in a given year
{
$facet: {
"categories": [
{
$group: {
_id: '$category',
count: { $sum: 1 }
}
},
// Sort by year descending
{ $sort: { count: -1, _id: -1 } }
],
"Total": [
{ "$match" : { "category": { "$exists": true }}},
{ "$count": "Total" },
],
}}]).toArray( (err, result) => {
if (err) return console.log(err)
console.log(result)
res.send(result)
})
})

MongoDB query on two collections to add/subtract a field in first collection with matched field from second collection

I've been stuck on this for a couple days and can't seem to figure it out.
Here's a couple example collections in my MongoDB database:
(just an example. sorry if they're not formatted correctly)
Products :
{
"_id": {
"$oid": "5e4633eaa7095f26d44a43c3"
},
"name": "Acrylic Frame Magnetic",
"units: "50",
"fbasku": "AFM-CL-0507-FBA",
"upc": "642709233954"
},
{
"_id": {
"$oid": "5e4633eaa7095f26d44a43c4"
},
"name": "Apron Polka Dot",
"units: "488",
"fbasku": "APD-RD-03PC-FBA",
"upc": "642709233961"
},
{
"_id": {
"$oid": "5e4633eaa7095f26d44a43c5"
},
"name": "Acrylic Sign Holder - 5x7",
"units: "632",
"fbasku": "ASH-GD-0507-FBA",
"upc": "642709233978"
}
Transactions :
{
"_id": {
"$oid": "5e44e8ed8f4cd40bd09d5ce8"
},
"type": "adjust",
"fbasku": "AFM-CL-0507-FBA",
"units": {
"$numberInt": "25"
},
"comment": "example transaction"
},
{
"_id": {
"$oid": "5e4c9ab642c5a232042f4b67"
},
"type": "send",
"fbasku": "AFM-CL-0507-FBA",
"units": {
"$numberInt": "75"
},
"comment": ""
},
{
"_id": {
"$oid": "5e4c9ab642c5a232042f4b69"
},
"type": "send",
"fbasku": "AFM-CL-0507-FBA",
"units": {
"$numberInt": "5"
},
"comment": ""
}
This is for a REST api I'm trying to build, so when I send a request to get all products, I want to:
Get an object containing all the products
Look through the transactions collection for each product in the object above, and add/subtract units from the listed unit quantity.
Give a response containing an object with the "fbasku", and a new calculated unit quantity for each product.
I got stuck when it came to all the async/await and Promise stuff, don't really understand it
You can try below code - it is a sample of how to do your requirement using native nodejs-mongodb driver and node.js :
const MongoClient = require('mongodb').MongoClient;
const dbConnection = async function () {
/** Connection URL */
const url = 'mongodb://localhost:27017/test'; // Check whether you're passing correct DB name & full formed connection string
let client;
try {
/** Use connect method to connect to the Server */
client = await MongoClient.connect(url);
const db = client.db(); // MongoDB would return client and you need to call DB on it.
let dbResp = await db.collection('Products').aggregate([
{
$lookup:
{
from: "Transactions",
let: { fbasku: "$fbasku" },
pipeline: [
{
$match:
{
$expr:
{ $eq: ["$fbasku", "$$fbasku"] }
}
},
{ $project: { units: 1, _id: 0 } }
],
as: "transaction_docs"
}
}, {
$project: {
_id: 0, fbasku: 1,
units: {
$add: [{ $toInt: '$units' }, {
$reduce: {
input: '$transaction_docs',
initialValue: 0,
in: { $add: ["$$value", "$$this.units"] }
}
}]
}
}
}
]).toArray();
client.close();
return dbResp;
} catch (err) {
(client) && client.close();
console.log(err);
throw err
}
};
/** In general dbConnection() has to be in one config file & it needs to be imported where ever db transactions are done like below */
dbConnection().then(res => console.log('Printing at calling ::', res)).catch(err => console.log('Err at Calling ::', err));
Note : This code has query which assumes units in products is a string, So we're converting it into int & adding that values to (sum of units of transactions => intOf(units of a products) + (sum of units of all matched transactions)).
Test Query : MongoDB-Playground

Create and update field Mongoose NodeJs

{
"_id": {
"$oid": "5a4e5b1d09fb590058bfdf86"
},
"name": "ProjectStore",
"imageURL": "none",
"longitude": 0,
"latitude": 0,
"rating": 5,
"leads": [
{
"customerId": "5a0c57db65a4931768716566",
"customerName": "testuser",
"interested": "testuser",
"_id": {
"$oid": "5a4e5b5409fb590058bfdf88"
}
}
],
"items": [
{
"name": "chat",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5f2235680012bd12cb",
"_id": {
"$oid": "5a4e5b3009fb590058bfdf87"
}
}
],
"__v": 2
}
I added my DB log, I try to write query that will let me update/push information to "LeadStatus".
This varible should be inside each object in the "leads" array.
I have the keys for the main id "5a4e5b1d09fb590058bfdf86"
I have the second key for the specific lead "5a4e5b5409fb590058bfdf88"
I just dont know how to write the query, for now i got this.... and got error.
Store.update(
{ _id: req.body.store._id, 'leads._id': 'req.body.lead._id', },
{ $set: { 'leads.$.LeadStatus': 'open' }, },
(err, result) => {
if (err) {
res.status(500)
.json({ error: 'Unable to update leads.', });
} else {
res.status(200)
.json(result);
}
}
);
Please Help,
Thanks.
I'm assuming you want the updated doc returned (you can choose to not do so as well, switch option new to false). You have to assign an object the value that you're updating and provide all the fields within the subfield you're updating, otherwise it will remove everything else. That's why you have to do a findOne first:
return Store.findOne({ '_id': "5a4e5b1d09fb590058bfdf86" })
.then((store) => {
if (!store) { // patient was not found for some reason
throw new Error(`Store id was not found`);
}
const options = {
new: true,
};
const updatedStore = Object.assign({}, store.toObject(), {
leads: [
{
"leadStatus": "open",
"customerId": "5a0c57db65a4931768716566",
"customerName": "testuser",
"interested": "testuser",
"_id": {
"$oid": "5a4e5b5409fb590058bfdf88"
}
}
],
});
return Store.findOneAndUpdate(
{ '_id': req.body.store._id },
{ $set: updatedStore },
options);
});
In case you're curious, you can build pre/virtual hooks off of findOneAndUpdate (no documentation for this), you won't lose anything from not using update.

Update many documents in mongoDB with different values

I am trying to update two documents in mongoDB, with two different values. I made it with two different callbacks, but is it possible to do it with only one request?
My solution:
mongo.financeCollection.update(
{ 'reference': 10 },
{ $push:
{ history: history1 }
}, function (err){
if (err){
callback (err);
}
else {
mongo.financeCollection.update(
{ 'reference': 20 },
{ $push:
{ history: history2 }
}, function (err){
if (err){
callback(err);
}
else {
callback(null);
}
});
}
});
Sorry if it is a stupid question but I just want to optimize my code!
Best to do this update using the bulkWrite API. Consider the following example for the above two documents:
var bulkUpdateOps = [
{
"updateOne": {
"filter": { "reference": 10 },
"update": { "$push": { "history": history1 } }
}
},
{
"updateOne": {
"filter": { "reference": 20 },
"update": { "$push": { "history": history2 } }
}
}
];
mongo.financeCollection.bulkWrite(bulkUpdateOps,
{"ordered": true, "w": 1}, function(err, result) {
// do something with result
callback(err);
}
The {"ordered": true, "w": 1} ensures that the documents will be updated on the server serially, in the order provided and thus if an error occurs all remaining updates are aborted. The {"w": 1} option determines the write concern with 1 being a request acknowledgement that the write operation has propagated to the standalone mongod or the primary in a replica set.
For MongoDB >= 2.6 and <= 3.0, use the Bulk Opeartions API as follows:
var bulkUpdateOps = mongo.financeCollection.initializeOrderedBulkOp();
bulkUpdateOps
.find({ "reference": 10 })
.updateOne({
"$push": { "history": history1 }
});
bulkUpdateOps
.find({ "reference": 20 })
.updateOne({
"$push": { "history": history2 }
});
bulk.execute(function(err, result){
bulkUpdateOps = mongo.financeCollection.initializeOrderedBulkOp();
// do something with result
callback(err);
});

MongoDB: Update property of subarray just updates the first element

The matching element looks like that:
{
"_id": {
"$oid": "519ebd1cef1fce06f90e3157"
},
"from": "Tester2",
"to": "Tester",
"messages": [
{
"username": "Tester2",
"message": "heeey",
"read": false
},
{
"username": "Tester",
"message": "hi!",
"read": false
},
{
"username": "Tester2",
"message": "test",
"read": false
}
],
}
Now I try to set the read property to the current date just of the subelements where the username is not equal to Tester:
var messages = db.collection('messages');
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
messages: {
$elemMatch: { username: { $ne: "Tester" } }
}
},
{ $set: { 'messages.$.read': new Date() } },
{ multi: true }, function(error, result) {
console.log(error);
console.log(result);
});
But just the first messages subelement read property updates:
{
"_id": {
"$oid": "519ebd1cef1fce06f90e3157"
},
"from": "Tester2",
"to": "Tester",
"messages": [
{
"username": "Tester2",
"message": "heeey",
"read": {
"$date": "2013-01-01T00:00:00.000Z"
}
},
{
"username": "Tester",
"message": "hi!",
"read": false
},
{
"username": "Tester2",
"message": "test",
"read": false
}
],
}
What's wrong with the code?
I'm using node.js v0.10.8 and MongoDB v2.4.3 together with node-mongodb-native.
There's nothing wrong with your code; the $ operator contains the index of the first matching array element. The {multi: true} option only makes the update apply to multiple documents, not array elements. To update multiple array elements in a single update you must specify them by numeric index.
So you'd have to do something like this:
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157")
},
{ $set: {
'messages.0.read': new Date(),
'messages.2.read': new Date()
} },
function (err, result) { ... }
);
This is similar to question: How to Update Multiple Array Elements in mongodb
var set = {}, i, l;
for(i=0,l=messages.length;i<l;i++) {
if(messages[i].username != 'tester') {
set['messages.' + i + '.read'] = new Date();
}
}
.update(objId, {$set:set});
I think ArrayFilters can be used in this case
I know it's a little bit late to answer this question but if you use the $[] operator, it will do the trick.
more details here
https://www.mongodb.com/docs/manual/reference/operator/update/positional-all/
I was able to use it inside an updateMany operation, but it should work also for update
var messages = db.collection('messages');
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
messages: {
$elemMatch: { username: { $ne: "Tester" } }
}
},
{ $set: { 'messages.$[].read': new Date() } },
{ multi: true }, function(error, result) {
console.log(error);
console.log(result);
});

Resources