How to calculate ratios for an additive attribute with mongodb? - mongodb

Using the sample mongodb aggregation collection (http://media.mongodb.org/zips.json), I would like to output the population share of every city in California.
In SQL, it could look like this:
SELECT city, population/SUM(population) as poppct
FROM (
SELECT city, SUM(population) as population
FROM zipcodes
WHERE state='CA'
GROUP BY city
) agg group by state;
This can be done using mongodb map/reduce:
db.runCommand({
mapreduce : "zipcodes"
, out : { inline : 1}
, query : {state: "CA"}
, map : function() {
emit(this.city, this.pop);
cache.totalpop = cache.totalpop || 0;
cache.totalpop += this.pop;
}
, reduce : function(key, values) {
var pop = 0;
values.forEach(function(value) {
if (value && typeof value == 'number' && value > 0) pop += value;
});
return pop;
}
, finalize: function(key, reduced) {
return reduced/cache.totalpop;
}
, scope: { cache: { } }
});
Can this be also achieved using the new aggregation framework (v2.2)? This would require some form of global scope, as in the map/reduce case.
Thanks.

Is this what you're after?
db.zipcodes.remove();
db.zipcodes.insert([
{ city:"birmingham", population:1500000, state:"AL" },
{ city:"London", population:10000, state:"ON" },
{ city:"New York", population:1000, state:"NY" },
{ city:"Denver", population:100, state:"CO" },
{ city:"Los Angeles", population:1000000, state:"CA" },
{ city:"San Francisco", population:2000000, state:"CA" },
]);
db.zipcodes.runCommand("aggregate", { pipeline: [
{ $match: { state: "CA" } }, // WHERE state='CA'
{ $group: {
_id: "$city", // GROUP BY city
population: { $sum: "$population" }, // SUM(population) as population
}},
]});
produces
{
"result" : [
{
"_id" : "San Francisco",
"population" : 2000000
},
{
"_id" : "Los Angeles",
"population" : 1000000
}
],
"ok" : 1
}

you could try:
db.zipcodes.group( { key: { state:1 } ,
reduce: function(curr, result) {
result.total += curr.pop;
result.city.push( { _id: curr.city, pop: curr.pop } ); },
initial: { total: 0, city:[] },
finalize: function (result) {
for (var idx in result.city ) {
result.city[idx].ratio = result.city[idx].pop/result.total;
}
} } )

Related

Statistics with Mongo DB

I have the following DB structure :
{
"uploadedAt": "2021-09-22T22:09:12.133Z",
"paidAt: "2021-09-30T22:09:12.133Z",
"amount": {
"currency": "EUR",
"expected": 70253,
"paid": 0
},
}
I would like to know how do I calculate the total amount that still need to be paid (expected - paid), and the average date between uploadedAt and paidAt. This for multiple records.
My function for getting the data is (the criteria should be updated to get this data).
const invoiceParams = new FindParams();
invoiceParams.criteria = { company: company._id }
const invoices = await this.findAll(invoiceParams);
FindAll function looks like:
async findAll(
params: FindParams,
ability?: Ability,
includeDeleted: boolean = false,
): Promise<Entity[]> {
let queryCriteria: Criteria = params.criteria;
let query: DocumentQuery<Entity[], Entity> = null;
if (!includeDeleted) {
queryCriteria = {
...queryCriteria,
deleted: { $ne: true },
};
}
try {
if (ability) {
ability.throwUnlessCan('read', this.entityModel.modelName);
queryCriteria = {
...toMongoQuery(ability, this.entityModel.modelName),
...queryCriteria,
};
}
query = this.entityModel.find(queryCriteria);
if (params.populate) {
query = query.populate(params.populate);
}
if (params.sort) {
query = query.sort(params.sort);
}
if (params.select) {
query = query.select(params.select);
}
return query.exec();
} catch (error) {
if (error instanceof ForbiddenError) {
throw new ForbiddenException(error.message);
}
throw error;
}
}
Update:
const paymentTime = await this.invoiceModel.aggregate([
{
$group: {
_id: "$account",
averageSpread: { $avg: { $subtract: ["$paidAt", "$uploadedAt"] } },
count: { $sum: 1 }
}
}
]);
Try this aggregation pipeline:
db.invoiceParams.aggregate([
{
$set: {
expectedPaid: { $subtract: ["$amount.expected", "$amount.paid"] },
averageDate: { $toDate: { $avg: [{ $toLong: "$uploadedAt" }, { $toLong: "$paidAt" }] } }
}
}
])

MongoDb Titlecase in Collection

In my collection i need to change the firstname and lastname to be in Titlecase.since its in nested array i couldn't proceed.
db.users.find()
{
"users" : {
"assigned" :[
{
"firstName" : "naveen",
"lastName" : "bala",
},
{
"firstName" : "SHAJU",
"lastName" : "HARI",
},
{
"firstName" : "PADMANESH",
"lastName" : "NC",
}
]
}
}
I need the result to be like
{
"firstName" : "Padmanesh",
"lastName" : "Nc",
}
Tried this code below
function titleCase(str) {
return str && str.toLowerCase().split(/\s/).map(function(word) {
return word && word.replace(word[0], word[0].toUpperCase());
}).join(' ');
}
db.users.find().forEach(function(doc){
db.users.updateOne(
{ "_id": doc._id },
{ "$set": { "firstName": titleCase(doc.firstName) } }
);
});
The most efficient way is to use updateMany(). You can see how the titleCase operators work here: https://mongoplayground.net/p/xdePfeBvIQ1
https://docs.mongodb.com/master/reference/method/db.collection.updateMany/index.html
This should do it for you, you can match using the first arg if needed.
Please double check the user schema is correct in your question. If its not this will need to be tweaked. It expects each user doc contains a users object with an assigned property.
db.users.updateMany({}, [{
$set: {
"users.assigned": {
$map: {
input: "$users.assigned",
in: {
firstName: {
$concat:[
{$toUpper: {$substrCP: ["$$this.firstName", 0, 1]}},
{$toLower: {$substrCP: ["$$this.firstName", 1, {$strLenCP: "$$this.firstName"}]}},
]
},
lastName: {
$concat:[
{$toUpper: {$substrCP: ["$$this.lastName", 0, 1]}},
{$toLower: {$substrCP: ["$$this.lastName", 1, {$strLenCP: "$$this.lastName"}]}},
]
}
}
}
}
}
}])
An alternative, to do it on the mongo shell :
var titleCase = function (str) {
return (
str &&
str
.toLowerCase()
.split(/\s/)
.map(function (word) {
return word && word.replace(word[0], word[0].toUpperCase());
})
.join(" ")
);
};
db.users.find().forEach(function (doc) {
var a = doc.users.assigned;
a.forEach(function (person, index) {
var setop = `users.assigned.` + index + `.firstName`;
var uppered = titleCase(person.firstName);
db.users.updateOne(
{ _id: doc._id, "users.assigned.firstName": person.firstName },
{ $set: { [setop]: uppered } }
);
});
});

MongoDB - convert double to string with aggregation

I am new to MongoDB and I am trying to convert double to string. I am not sure why my results are not as needed.
export function StoreSettings(req, res) {
var id = req.params.id;
var id = mongoose.Types.ObjectId(id);
Setting.aggregate([
{
$match: { restaurantID: id }
},
{
$addFields: {
"appTheme.appBanner": {
$concat: [
"/App/Carousel/",
{ $toString: "$appTheme.appBanner" },
".png"
]
}
}
}
])
.exec()
.then(data => {
return res.json(data);
})
.catch(err => res.json({ data: "Data Not Found", err }));
}
==OUTPUT==
{
"_id": "5e3379be06558d0c40d035ee",
"appTheme": {
"appBanner": "/App/Carousel/1.58078e+12.png"
}}
=== i NEED it to be like this: ====
{
"_id": "5e3379be06558d0c40d035ee",
"appTheme": {
"appBanner": "/App/Carousel/1580782209156.png"
}}
what am i doing wrong?
Thanks!
As $appTheme.appBanner :1580782209156 is a double in database, then using $toString would result in 1.58078e+12. You need to convert it into NumberLong() using $toLong & then convert it to string, Try below :
Setting.aggregate([
{
$match: { restaurantID: id }
},
{
$addFields: {
"appTheme.appBanner": {
$concat: [
"/App/Carousel/",
{ $toString: { $toLong: "$appTheme.appBanner" } },
".png"
]
}
}
}
])
Test : MongoDB-Playground

MongoDB push to array with predefined index

How do I add an item to Mongoose, if I want to push it to an item of the array?
I want to push it to the document with predefined _id, to the 'productList' array with predefined 'id', to the 'items' array.
{
"_id" : ObjectId("5ba94316a48a4c828788bcc9"),
"productList" : [
{
"id" : 1,
"items" : [
{
"id" : 1,
"name" : "FLOSS 500",
}
]
}
]
}
I thought that it should be something like this, but it did not work:
Products.findOneAndUpdate({_id: req.body._id, productList: {id: req.body.id}}, {$push: {'items': req.body.product}})
You can try this with positional operator $. For search by nested array property use dot-separated syntax:
Products.findOneAndUpdate({
_id: req.body._id,
'productList.id': req.body.id
}, { $push: { 'productList.$.items': req.body.product } });
Full example:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const Products = mongoose.model('Test', new Schema({
productList: []
}));
mongoose.connect("mongodb://localhost:27017/myapp");
let item = new Products({
"_id": mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
"productList": [
{
"id": 1,
"items": [
{
"id": 1,
"name": "FLOSS 500",
}
]
}
]
});
Products.deleteMany({}).then(() => {
return Products.create(item);
}).then(() => {
return Products.findOneAndUpdate({
_id: mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
'productList.id': 1
}, {
$push: {
'productList.$.items': {
"id": 2,
"name": "FLOSS 600",
}
}
});
}).then(() => {
return Products.find({
_id: mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
'productList.id': 1
});
}).then(data => {
console.log(data);
if (data) {
console.log(data[0].productList);
/* [{"id":1,"items":[{"id":1,"name":"FLOSS 500"},{"id":2,"name":"FLOSS 600"}]}] */
}
}).catch(err => {
console.error(err);
});

Sailsjs native with Mapreduce

I am working on sailsjs project, i just looking for suggestion to achieve the below output to make best performance with code samples.
My existing collection having this below document.
[{
"word" : "DAD",
"createdAt":"6/10/2016 7:25:59 AM",
"gamescore":1
},
{
"word" : "SAD",
"createdAt":"6/09/2016 7:25:59 AM",
"gamescore":1
},
{
"word" : "PAD",
"createdAt":"6/10/2016 8:25:59 AM",
"gamescore":1
}]
I need the below output which is something like this.
[{
"word" : "A",
"repeatedTimes" : "3",
"LatestRepeatedTime": "6/10/2016 8:25:59 AM"
},
{
"word" : "D",
"repeatedTimes" : "4",
"LatestRepeatedTime": "6/10/2016 8:25:59 AM"
},
{
"word" : "P",
"repeatedTimes" : "1",
"LatestRepeatedTime": "6/10/2016 8:25:59 AM"
},
{
"word" : "S",
"repeatedTimes" : "1",
"LatestRepeatedTime": "6/09/2016 8:25:59 AM"
}]
For the above scenario i implemented the below code to fetch, but it is not working at find query.
var m = function () {
var words = this.word;
if (words) {
for (var i = 0; i < words.length; i++) {
emit(words[i], 1);
}
}
}
var r = function (key, values) {
var count = 0;
values.forEach(function (v) {
count += v;
});
return count;
}
console.log(req.params.childid);
Activity.native(function (err, collection) {
console.log("hello");
collection.mapReduce(m, r, {
out: {merge: "words_count" + "_" + "575a4952bfb2ad01481e9060"}
}, function (err, result) {
Activity.getDB(function (err, db) {
var colname = "words_count" + "_" + "575a4952bfb2ad01481e9060";
var natCol = db.collection('words_count' + "_" + "575a4952bfb2ad01481e9060");
natCol.find({},..... **is not working**
natCol.count({}, function (err, docs) {
console.log(err);
console.log(docs);
res.ok(docs);
});
});
});
});
Answer:
natCol.aggregate([
{
$project:
{
_id: "$_id" ,
value:"$value"
}
}
], function(err, data){
console.log(data);
res.ok(data);
});
You could try the following
var m = function () {
if (this.word) {
for (var i = 0; i < this.word.length; i++) {
emit(this.word[i], {
"repeatedTimes": 1,
"LatestRepeatedTime": this.createdAt
});
}
}
};
var r = function (key, values) {
var obj = {};
values.forEach(function(value) {
printjson(value);
Object.keys(value).forEach(function(key) {
if (!obj.hasOwnProperty(key)) obj[key] = 0;
if (key === "repeatedTimes") obj[key] += value[key];
});
obj["LatestRepeatedTime"] = value["LatestRepeatedTime"];
});
return obj;
};
var opts = { out: {inline: 1} };
Activity.native(function (err, collection) {
collection.mapReduce(m, r, opts, function (err, result) {
console.log(err);
console.log(result);
res.ok(result);
});
});