mongodb insert then conditional sum followed by updating sum to another collection - mongodb

I am trying to
insert a new record with a points field in to reputationActivity collection
get sum of points from reputationActivity collection where user id matches
insert the resulting sum to users collection
Here is mongo playground which does not work right now - https://mongoplayground.net/p/tHgPpODjD6j
await req.db.collection('reputationActivity').insertOne({
_id: nanoid(),
"userId": userId,//insert a new record for this user
"points": points,//insert integer points
},
function(){
req.db.collection('reputationActivity').aggregate([ { $match: { userId: userId } },
{ TotalSum: { $sum: "$points" } } ]); // sum of point for this user
req.db.collection('users').updateOne(
{_id: userId},
{$set: {"userTotalPoints": TotalSum}},// set sum in users collection
)
}
)
});
The above code gives me an error that Total sum is not defined. Is it better to do this without a callback function and if so, how?

write in one single block of command, use drop command carefully. it's used here to show start to end and illustration purposes only.
> db.reputation.drop();
true
> db.reputation.insertOne(
... {_id: 189, "userId": 122, "points": 60});
{ "acknowledged" : true, "insertedId" : 189 }
> var v_userId = 122;
> db.reputation.aggregate([
... {
... $match: {
... userId: v_userId
... }
... },
... {
... $group: {
... "_id": null, "TotalSum": {$sum: "$points"}}
... },
... ]).forEach(function(doc)
... {print("TotalSum: " +doc.TotalSum,
... "userId: " +v_userId);
... db.users.updateOne(
... {"userId":v_userId}, {$set:{"userPoints":doc.TotalSum}});
... }
... );
TotalSum: 60 userId: 122
> db.users.find();
{ "_id" : 189, "userId" : 122, "points" : 60, "userPoints" : 60 }
>

Related

MongoQuery to update document using addition

I have the following document in student collection:
{
"uid": 1,
"eng": 70
}
Now I want to add 10 into eng field and want result 80. to do this I am using following query:
db.getCollection('student').aggregate([{$match:{uid:1}},{$set:{eng:{$sum:10}}}])
but it is not working. SO how can add any number in the field to the required output? is any addition query in MongoDB. help me here
I suggest using the $inc operator here:
db.getCollection('student').update(
{ uid: 1 },
{ $inc: { eng: 10 } }
)
SOLUTION #1: Set sum to the same field eng.
db.student.aggregate([
{ $match: { uid: 1 } },
{
$set: {
eng: { $add: ["$eng", 10] } // $sum: ["$eng", 10] Also works;)
}
}
])
Output:
{
"_id" : ObjectId("6065f94abb72032a689ed61d"),
"uid" : 1,
"eng" : 80
}
SOLUTION #2: Set sum to a different field result.
Using $addFields add result filed.
Using $add add 10 to eng and store it in result.
db.student.aggregate([
{ $match: { uid: 1 } },
{
$addFields: {
result: { $add: ["$eng", 10] }
}
}
])
Output:
{
"_id" : ObjectId("6065f94abb72032a689ed61d"),
"uid" : 1,
"eng" : 70,
"result" : 80
}

Mongo db Group by, count with a condition

Using Mongodb, I want to get the count of sensor values above 100 and sensorvalues below 100 for each particular region(group by region).
I have a sensorValue property and it has 4 sub properties namely.
1)sensorValue (the values will be 100, 200 122, 80 etc) - I want to know the count of above 100 and below 100 per region.
2)Latitude
3)Longitude
4)Region (The name of the region) - I want the count with respect to this region.
With the help of stackoverflow, I wrote the below query.
getProximityIntervalRate = (req, res) => {
console.log("entered1")
this.model = ProximityLocation;
const startDate = req.headers.startdate, endDate = req.headers.enddate;
console.log(req.headers, startDate, endDate);
// TODO: server validatoin
this.model.aggregate([
{ $match: { 'observationTimestamp': { $gte: new Date(startDate), $lte: new Date(endDate) } } },
{
$project: {
regoin: 1,
lessthan: {
$cond: [{ $lt: ["$sensorValue.sensorValue", 5] }, 1, 0]
},
morethan: {
$cond: [{ $gt: ["$sensorValue.sensorValue", 5] }, 1, 0]
}
}
},
{
$group: { _id: { regoin: "$sensorValue.regoin" },
countSmaller: { $sum: "$lessThan" },
countBigger: { $sum: "$moreThan" } uh
}
},
], (err, location) => {
console.log('location', location);
if (!location) { return res.sendStatus(404); }
res.status(200).json(location);
});
}
I am not sure how to address the subproperty "sensorValue.regoin" under the "$project" option.Please let me know if I am missing something.
You can try below aggregation to get the result
db.t66.aggregate([
{$group: {
_id : "$sensorValue.region",
lessThan : {$sum : {$cond: [{$lt : [{$toInt : "$sensorValue.sensorValue"}, 50]}, 1,0]}},
greaterThan : {$sum : {$cond: [{$gte : [{$toInt : "$sensorValue.sensorValue"}, 50]}, 1,0]}},
}}
])
you can remove $toInt if the sensorValue is int datatype

Mongodb: Converting numeric string to number

How can we convert a numeric string to number in Mongodb?
Actual Problem:
Collection: try
Two Sample documents stored in try collection:
{
_id: "1"
testField: 150
}
{
_id: "A"
testField: 140
}
I want to filter out the _id field in the project phase for further processing in group phase. The query below is working fine but I need better solution for it like using type or any other method.
db.try.aggregate([{$match: {"_id":{$in:["1","2","3","4","5","6","7","8","9"]}}}, {$group:{"_id":0, total: {$sum:"$testField"}}}])
You can use regex in this case:
db.try.aggregate([{$match: {"_id":
{$in:[/\d+/]}}},
{$group:{"_id":0, total: {$sum:"$testField"}}}])
An alternative is to use MapReduce where in your map function you can check whether an _id contains a number:
Sample documents:
db.try.insert([
{
_id: "1",
testField: 150
},
{
_id: "2",
testField: 150
},
{
_id: "A",
testField: 140
},
{
_id: "B",
testField: 140
}
]);
MapReduce:
var map = function(){
var numeric_id = !isNaN(this._id) ? "numeric" : "non_numeric";
emit(numeric_id, this.testField);
};
var reduce = function(key, values) {
return Array.sum(values);
};
db.try.mapReduce(
map,
reduce,
{ out: "try_totals" }
);
Result:
db.try_totals.find()
/* 0 */
{
"_id" : "non_numeric",
"value" : 280
}
/* 1 */
{
"_id" : "numeric",
"value" : 300
}

MongoDB - Query all documents createdAt within last hours, and group by minute?

From reading various articles out there, I believe this should be possible, but I'm not sure where exactly to start.
This is what I'm trying to do:
I want to run a query, where it finds all documents createAt within the last hour, and groups all of them by minute, and since each document has a tweet value, like 5, 6, or 19, add them up for each one of those minutes and provides a sum.
Here's a sample of the collection:
{
"createdAt": { "$date": 1385064947832 },
"updatedAt": null,
"tweets": 47,
"id": "06E72EBD-D6F4-42B6-B79B-DB700CCD4E3F",
"_id": "06E72EBD-D6F4-42B6-B79B-DB700CCD4E3F"
}
Is this possible to do in mongodb?
#zero323 - I first tried just grouping the last hour like so:
db.tweetdatas.group( {
key: { tweets: 1, 'createdAt': 1 },
cond: { createdAt: { $gt: new Date("2013-11-20T19:44:58.435Z"), $lt: new Date("2013-11-20T20:44:58.435Z") } },
reduce: function ( curr, result ) { },
initial: { }
} )
But that just returns all the tweets within the timeframe, which technically is what I want, but now I want to group them all by each minute, and add up the sum of tweets for each minute.
#almypal
Here is the query that I'm using, based off your suggestion:
db.tweetdatas.aggregate(
{$match:{ "createdAt":{$gt: "2013-11-22T14:59:18.748Z"}, }},
{$project: { "createdAt":1, "createdAt_Minutes": { $minute : "$createdAt" }, "tweets":1, }},
{$group:{ "_id":"$createdAt_Minutes", "sum_tweets":{$sum:"$tweets"} }}
)
However, it's displaying this response:
{ "result" : [ ], "ok" : 1 }
Update: The response from #almypal is working. Apparently, putting in the date like I have in the above example does not work. While I'm running this query from Node, in the shell, I thought it would be easier to convert the var date to a string, and use that in the shell.
Use aggregation as below:
var lastHour = new Date();
lastHour.setHours(lastHour.getHours()-1);
db.tweetdatas.aggregate(
{$match:{ "createdAt":{$gt: lastHour}, }},
{$project: { "createdAt":1, "createdAt_Minutes": { $minute : "$createdAt" }, "tweets":1, }},
{$group:{ "_id":"$createdAt_Minutes", "sum_tweets":{$sum:"$tweets"} }}
)
and the result would be like this
{
"result" : [
{
"_id" : 1,
"sum_tweets" : 117
},
{
"_id" : 2,
"sum_tweets" : 40
},
{
"_id" : 3,
"sum_tweets" : 73
}
],
"ok" : 1
}
where _id corresponds to the specific minute and sum_tweets is the total number of tweets in that minute.

Search on multiple collections in MongoDB

I know the theory of MongoDB and the fact that is doesn't support joins, and that I should use embeded documents or denormalize as much as possible, but here goes:
I have multiple documents, such as:
Users, which embed Suburbs, but also has: first name, last name
Suburbs, which embed States
Child, which embeds School, belongs to a User, but also has: first name, last name
Example:
Users:
{ _id: 1, first_name: 'Bill', last_name: 'Gates', suburb: 1 }
{ _id: 2, first_name: 'Steve', last_name: 'Jobs', suburb: 3 }
Suburb:
{ _id: 1, name: 'Suburb A', state: 1 }
{ _id: 2, name: 'Suburb B', state: 1 }
{ _id: 3, name: 'Suburb C', state: 3 }
State:
{ _id: 1, name: 'LA' }
{ _id: 3, name: 'NY' }
Child:
{ _id: 1, _user_id: 1, first_name: 'Little Billy', last_name: 'Gates' }
{ _id: 2, _user_id: 2, first_name: 'Little Stevie', last_name: 'Jobs' }
The search I need to implement is on:
first name, last name of Users and Child
State from Users
I know that I have to do multiple queries to get it done, but how can that be achieved? With mapReduce or aggregate?
Can you point out a solution please?
I've tried to use mapReduce but that didn't get me to have documents from Users which contained a state_id, so that's why I brought it up here.
This answer is outdated. Since version 3.2, MongoDB has limited support for left outer joins with the $lookup aggregation operator
MongoDB does not do queries which span multiple collections - period. When you need to join data from multiple collections, you have to do it on the application level by doing multiple queries.
Query collection A
Get the secondary keys from the result and put them into an array
Query collection B passing that array as the value of the $in-operator
Join the results of both queries programmatically on the application layer
Having to do this should be rather the exception than the norm. When you frequently need to emulate JOINs like that, it either means that you are still thinking too relational when you design your database schema or that your data is simply not suited for the document-based storage concept of MongoDB.
So now join is possible in mongodb and you can achieve this using $lookup and $facet aggregation here and which is probably the best way to find in multiple collections
db.collection.aggregate([
{ "$limit": 1 },
{ "$facet": {
"c1": [
{ "$lookup": {
"from": Users.collection.name,
"pipeline": [
{ "$match": { "first_name": "your_search_data" } }
],
"as": "collection1"
}}
],
"c2": [
{ "$lookup": {
"from": State.collection.name,
"pipeline": [
{ "$match": { "name": "your_search_data" } }
],
"as": "collection2"
}}
],
"c3": [
{ "$lookup": {
"from": State.collection.name,
"pipeline": [
{ "$match": { "name": "your_search_data" } }
],
"as": "collection3"
}}
]
}},
{ "$project": {
"data": {
"$concatArrays": [ "$c1", "$c2", "$c3" ]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
You'll find MongoDB easier to understand if you take a denormalized approach to schema design. That is, you want to structure your documents the way the requesting client application understands them. Essentially, you are modeling your documents as domain objects with which the applicaiton deals. Joins become less important when you model your data this way. Consider how I've denormalized your data into a single collection:
{
_id: 1,
first_name: 'Bill',
last_name: 'Gates',
suburb: 'Suburb A',
state: 'LA',
child : [ 3 ]
}
{
_id: 2,
first_name: 'Steve',
last_name: 'Jobs',
suburb: 'Suburb C',
state 'NY',
child: [ 4 ]
}
{
_id: 3,
first_name: 'Little Billy',
last_name: 'Gates',
suburb: 'Suburb A',
state: 'LA',
parent : [ 1 ]
}
{
_id: 4,
first_name: 'Little Stevie',
last_name: 'Jobs'
suburb: 'Suburb C',
state 'NY',
parent: [ 2 ]
}
The first advantage is that this schema is far easier to query. Plus, updates to address fields are now consistent with the individual Person entity since the fields are embedded in a single document. Notice also the bidirectional relationship between parent and children? This makes this collection more than just a collection of individual people. The parent-child relationships mean this collection is also a social graph. Here are some resoures which may be helpful to you when thinking about schema design in MongoDB.
Here's a JavaScript function that will return an array of all records matching specified criteria, searching across all collections in the current database:
function searchAll(query,fields,sort) {
var all = db.getCollectionNames();
var results = [];
for (var i in all) {
var coll = all[i];
if (coll == "system.indexes") continue;
db[coll].find(query,fields).sort(sort).forEach(
function (rec) {results.push(rec);} );
}
return results;
}
From the Mongo shell, you can copy/paste the function in, then call it like so:
> var recs = searchAll( {filename: {$regex:'.pdf$'} }, {moddate:1,filename:1,_id:0}, {filename:1} )
> recs
Based on #brian-moquin and others, I made a set of functions to search entire collections with entire keys(fields) by simple keyword.
It's in my gist; https://gist.github.com/fkiller/005dc8a07eaa3321110b3e5753dda71b
For more detail, I first made a function to gather all keys.
function keys(collectionName) {
mr = db.runCommand({
'mapreduce': collectionName,
'map': function () {
for (var key in this) { emit(key, null); }
},
'reduce': function (key, stuff) { return null; },
'out': 'my_collection' + '_keys'
});
return db[mr.result].distinct('_id');
}
Then one more to generate $or query from keys array.
function createOR(fieldNames, keyword) {
var query = [];
fieldNames.forEach(function (item) {
var temp = {};
temp[item] = { $regex: '.*' + keyword + '.*' };
query.push(temp);
});
if (query.length == 0) return false;
return { $or: query };
}
Below is a function to search a single collection.
function findany(collection, keyword) {
var query = createOR(keys(collection.getName()));
if (query) {
return collection.findOne(query, keyword);
} else {
return false;
}
}
And, finally a search function for every collections.
function searchAll(keyword) {
var all = db.getCollectionNames();
var results = [];
all.forEach(function (collectionName) {
print(collectionName);
if (db[collectionName]) results.push(findany(db[collectionName], keyword));
});
return results;
}
You can simply load all functions in Mongo console, and execute searchAll('any keyword')
You can achieve this using $mergeObjects by MongoDB Driver
Example
Create a collection orders with the following documents:
db.orders.insert([
{ "_id" : 1, "item" : "abc", "price" : 12, "ordered" : 2 },
{ "_id" : 2, "item" : "jkl", "price" : 20, "ordered" : 1 }
])
Create another collection items with the following documents:
db.items.insert([
{ "_id" : 1, "item" : "abc", description: "product 1", "instock" : 120 },
{ "_id" : 2, "item" : "def", description: "product 2", "instock" : 80 },
{ "_id" : 3, "item" : "jkl", description: "product 3", "instock" : 60 }
])
The following operation first uses the $lookup stage to join the two collections by the item fields and then uses $mergeObjects in the $replaceRoot to merge the joined documents from items and orders:
db.orders.aggregate([
{
$lookup: {
from: "items",
localField: "item", // field in the orders collection
foreignField: "item", // field in the items collection
as: "fromItems"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project: { fromItems: 0 } }
])
The operation returns the following documents:
{ "_id" : 1, "item" : "abc", "description" : "product 1", "instock" : 120, "price" : 12, "ordered" : 2 }
{ "_id" : 2, "item" : "jkl", "description" : "product 3", "instock" : 60, "price" : 20, "ordered" : 1 }
This Technique merge Object and return the result
Minime solution worked except that it required a fix:
var query = createOR(keys(collection.getName()));
need to add keyword as 2nd parameter to createOR call here.