Formatting MongoDB aggregation for Highcharts Stacked Columns - mongodb

I'm attempting to plot a highcharts stacked column chart displaying multiple button counts per table category. For this I've built the following aggregation in mongodb:
var pipeline = [
{
$group: {
_id: {tableId: "$tableId", buttonId: "$buttonId"},
count: {
$sum: 1
}
}
}
];
This gives me a JSON object with the following hierarchy:
0:
count: 5
_id:
buttonId: "buttonId1"
tableId: "Table1"
I'm trying to bring this into a format suited for highcharts, something along these lines:
var buttonPerTable = [{
name: 'buttonId1',
data: [5, 3, 4]
}, {
name: 'buttonId2',
data: [2, 2, 3]
}, {
name: 'buttonId3',
data: [3, 4, 4]
}]
var tableList = ["Table1","Table2","Table3"]
This is what I've tried, but I can't quite figure out how to do it right, I can't manage to get the data into the wished format:
var activityPerTable = [];
var tableList = [];
result.forEach(function(call, i) {
buttonId = call._id.buttonId
activityPerTable[buttonId] = []
activityPerTable[buttonId].data = []
activityPerTable[buttonId].name = buttonId;
activityPerTable[buttonId].data.push(call.count);
tableList.push(call._id.tableId);
});
tableList = Utils.uniqueArray(tableList);
Any help would be appreciated.
Edit:
Here is a chunk of JSON object:
[
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId1"
},
"count": 1
},
{
"_id": {
"tableId": "table2",
"buttonId": "buttonId3"
},
"count": 10
},
{
"_id": {
"tableId": "table2",
"buttonId": "buttonId1"
},
"count": 12
},
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId2"
},
"count": 8
},
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId2"
},
"count": 2
},
{
"_id": {
"tableId": "table3",
"buttonId": "buttonId1"
},
"count": 3
},
{
"_id": {
"tableId": "table3",
"buttonId": "buttonId2"
},
"count": 6
}
]

It would be a bit simpler if the same names were in order side by side. However, you can convert your JSON to the format required by Highcharts in this way:
Highcharts.each(data, function(el) {
if (series.length) {
for (i = 0; i < series.length; i++) {
if (series[i].name === el._id.buttonId) {
series[i].data.push(el.count);
i = series.length + 1;
}
}
}
if (!series.length || i === series.length) {
series.push({
name: el._id.buttonId,
data: [el.count]
});
}
});
Live demo: http://jsfiddle.net/BlackLabel/tnhv8u62/

Related

Group by and Get Max Value MongoDb

I would like to get the highest number of counts for each numId and display it on my front end in a table.
Here is an example of my database:
{
"_id": {
"$oid": "6294777f677b4c647e28771a"
},
"numId": "5",
"respondee": "0x9d95bcaa5b609fa97a7ec860bec115aa94f85ba9",
"__v": 0,
"originalResponse": "test2",
"submittedAt": {
"$date": {
"$numberLong": "1653897087357"
}
},
"addresses": [
"0x39c878a3df98002ddba477a7aa0609fb5a27e2ff",
"0xe3342d6522ad72f65d6b23f19b17e3fb12161f90"
],
"count": 2
},
{
"_id": {
"$oid": "6294836e677b4c647e287e93"
},
"numId": "5",
"respondee": "0xe3342d6522ad72f65d6b23f19b17e3fb12161f90",
"__v": 0,
"originalResponse": "test3",
"submittedAt": {
"$date": {
"$numberLong": "1653900142375"
}
},
"addresses": [
],
"count": 0
}
I have written something like this but I'm not sure how to group the results according to the numId
import Response from '../../../models/Response.model';
import db from '../../../utils/config/db';
import nc from 'next-connect';
import { onError } from '../../../utils/error';
const handler = nc({
onError,
});
//GET all
handler.get(async (req, res) => {
await db.connect();
let responses = await Response.find({ });
//To group responses by numId
// Sort responses by votes in ascending order
responses = responses.sort((a, b) => {
return a.count - b.count;
});
let topResponses = responses.filter((response) => {
return response.count === responses[0].count;
});
// Check if respondee has the highest count response
if (
topResponses.length > 0 &&
topResponses.find((response) => {
return response.respondee === respondee;
})
) {
// Get the response
let response = topResponses.find((response) => {
return response.respondee === respondee;
});
// Get the response
let responseString = response.response;
// Get the count
let count = response.count;
}
await db.disconnect();
});
export default handler;
I have figured out the answer by referring from another stackoverflow:
Group by and Get Max Value MongoDb
let responses = await Response.aggregate([
{ $sort: { votes: -1 } },
{ $group: { _id: '$baseId', group: { $first: '$$ROOT' } } },
{ $replaceRoot: { newRoot: '$group' } },
]);
res.send(responses);

Mongo: Multiple $inc doesn't work in Mongoose

I'm trying to make an $inc to 2 fields, points and sports.wins:
When I try to make it through the mongo shell, it works:
db.getCollection('userStats').update({
uid: ObjectId("5ed1bd8313955cbfc60df96f"),
"sports._id": ObjectId("5ed533c44dcb3efcfe8cb0ec")
},
{
"$inc": { "sports.$[sports].wins" : 1, "points": 10 },
},
{
"arrayFilters": [
{ "sports._id": ObjectId("5ed533c44dcb3efcfe8cb0ec") }
]
}
);
However, when I try to make it using bulkWrite (via Mongoose), it only updates the wins field:
let bulkArray = [];
bulkArray.push({
updateOne: {
filter: {
uid: usersWon[i].uid,
"sports._id": eventData.reference.sport
},
update: {
"$inc": {
"points": 10,
"sports.$[sport].wins": 1,
}
},
arrayFilters: [
{
"sport._id": sportId
}
]
}
});
await UserStats.bulkWrite(bulkArray);
What am I doing wrong? thanks!

How to multiply NumberDecimal values in mongodb

I have the following structure:
{
"_id": "5d0118f0f57a282f89bc5f71",
"product": {
"_id": "5cfed37375a13067dd01ddb7",
"name": "My product",
"description": "My description",
"purchased_amount": 15,
"unit_price_mex": "45",
"unit_price_to_sell": "5",
"travel": "5cf58713d6f7f1657e2d8302",
"__v": 0,
"id": "5cfed37375a13067dd01ddb7"
},
"client": {
"_id": "5cf1778efffb651fad89d8b6",
"name": "Client name",
"description": "",
"__v": 0
},
"purchased_amount": 3,
"fch": "13/6/2019",
"__v": 0
},
{
"_id": "5d0151afda1a446008f1817b",
"product": {
"_id": "5cfed1995eaf2665c45efd82",
"name": "Camisa",
"description": "Camisas buenas",
"purchased_amount": 10,
"unit_price_mex": "100",
"unit_price_to_sell": "15",
"travel": "5cf56b04462a865264fabb9d",
"__v": 0,
"id": "5cfed1995eaf2665c45efd82"
},
"client": {
"_id": "5cf1778efffb651fad89d8b6",
"name": "Randy",
"description": "El que trabaja aqui",
"__v": 0
},
"purchased_amount": 34,
"fch": "12/6/2019",
"__v": 0
},
Where client and product are of type ObjectId. This is the Schema:
Client Model
var mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
var clientSchema = new mongoose.Schema({
name: String,
description: String
}).plugin(mongoosePaginate);
var Client = mongoose.model('Client', clientSchema);
module.exports = Client;
Product Model
var mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
var productSchema = new mongoose.Schema({
name: String,
description: String,
purchased_amount: Number,
unit_price_mex: mongoose.Schema.Types.Decimal128,
unit_price_to_sell: mongoose.Schema.Types.Decimal128,
travel: { type: mongoose.Schema.Types.ObjectId, ref: 'Travel' }
}).plugin(mongoosePaginate);
productSchema.set('toJSON', {
getters: true,
transform: (doc, ret) => {
if (ret.unit_price_mex) {
ret.unit_price_mex = ret.unit_price_mex.toString();
}
if ( ret.unit_price_to_sell ) {
ret.unit_price_to_sell = ret.unit_price_to_sell.toString();
}
}
})
var Product = mongoose.model('Product', productSchema);
module.exports = Product;
I need to get the multiplication sum of purchased_amount by product.unit_price_to_sell. My code is the following but always returns 0. Apparently, "$product.unit_price_to_sell" does not return the decimal value.
var aggregate = InvoiceModel.aggregate([
{ $match: { client: mongoose.Types.ObjectId( id ) } },
{ $group: { _id: null, total: { $sum: { $multiply: [ "$purchased_amount", "$product.unit_price_to_sell" ] } } } }
]);
InvoiceModel.aggregatePaginate(aggregate, {}, (error, aggs) => {
InvoiceModel.paginate({ client: id },{ page, limit, populate: 'client product' }, (err, value) => {
return res.status(200).send({
results: value.docs,
totalPages: value.totalPages,
totalDocs: value.totalDocs,
purchase_amount_total : aggs.docs[0].total
})
})
})
MongoDB cannot use string values in arithmetic expressions. You must either store the values using their numeric non-string representations, or you must use an aggregation operator like $toDecimal to convert the values to their numeric representations first.
Modifying your $group stage to something like the following should work:
{ $group: { _id: null, total: { $sum: { $multiply: [ "$purchased_amount", { $toDecimal: "$product.unit_price_to_sell" } ] } } }
Please note, however, that this will only work for MongoDB versions >= 4.0. If you're using an older version of MongoDB, you will either need to upgrade it to at least version 4.0 or begin converting your existing values from strings to numbers.

How to find average using map reduce in MongoDB?

My document is of format:
{
"PItems": {
"Workspaces": [
{
"Key": "Item1",
"Size": 228.399,
"Foo": "bar"
},
{
"Key": "Item2",
"Size": 111.399,
"Bar": "baz"
},
{
"Key": "Item2",
"Size": 636.66,
"Baz": "foo"
}
]
}
}
I need to find the average size of each items from all the documents in the collection. How do I do that?
Expected output:
Item1: 346.12
Item2: 563.58
I have tried the following:
db.Resources.mapReduce(
function() {
this.PItems.Workspaces.forEach(
function(z) {
emit(z.Key, z.Size);
}
);
},
function(item, space) {
var total = 0;
for ( var i=0; i<space.length; i++ )
total += size[i];
return { avg : total/space.length };
},
out: { inline: 1 }
);
I am getting a syntax error: SyntaxError: missing ) after argument list. What am I missing here?

Finding multiple docs using same id not working, using meteor + react and mongoDB

How do I get the email address of the students in the same class_id, take it as there are more then 2 students in different class in the DB as well?
I have this but it return empty array []
Meteor.users.find({"course_learn_list.$.class_id": {$in: [classId]}},
{field: {"emails.address": 1}}
).fetch()
Collections
{
"_id": "LMZiLKs2MRhZiiwoS",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student1#gmail.com",
"verified": false
}
]
},
{
"_id": "JgfdLKs2MRhZJgfNgk",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student2#gmail.com",
"verified": false
}
]
}
I think you want:
Meteor.users.find({ "course_learn_list.class_id": classId },
{ "course_learn_list.$": 1, "emails.address": 1 }).fetch()
This should find the first instance in each course_learn_list array where the classId is your classId.
In this case you probably don't need to use a projection to get the right answer. Here's an example of extracting the verified email addresses using only the . operator in the selector:
const ids = ['jePhNgEuXLM3ZCt98', 'some-other-id'];
const emails =
Meteor.users.find({ 'course_learn_list.class_id': { $in: ids } })
.fetch()
.map(user => _.findWhere(user.emails, { verified: true }).address);
This works for me!
Meteor.publish("getMyClassStudents", function(classId) {
console.log("Publish getMyClassStudents")
var self = this
if (self.userId) {
var data = Meteor.users.find({
"course_learn_list.class_id": classId
}, {
"fields": {
"emails.address": 1
}
})
return data
}
else {
return self.ready()
}
})