MongoDB Reduce Key with single value - javascript

I want to count the number of orders by clients, and thus, on the last year, last month and last week.
I wrote a MapReduce program:
var mapOrders = function() {
var v_order = {
order_date : this.dt_order
...
};
emit(this.clientid, v_order);
};
var reduceOrders = function(p_clientid, p_orders) {
// Initialization of the output format of the couters
var r_result = { orders_count : {
total: {
1year: 0,
1month: 0,
7day: 0
}
...
}}
for (var c_order = 0; c_order < p_orders.length; c_order++) {
// Increment counters
}
return (r_result);
};
db.orders.mapReduce(
mapOrders,
reduceOrders,
{
out: { merge: "tmp_orders_indicators" }
}
)
In my output collection, I have 2 types of records
{
"_id" : 80320,
"value" : {
"order_date" : ISODate("2015-10-30T11:09:51.000Z")
...
}
}
{
"_id" : 80306,
"value" : {
"orders_count" : {
"total" : {
"count_1year" : 18,
"count_1month" : 6,
"count_7day" : 1
}
...
}
}
The clients with only 1 order don't go through the reduce function.
I found this in the MongoDB doucmentation that explain that behaviour:
MongoDB will not call the reduce function for a key that has only a
single value.
How can i do to have only 1 type of record in my output collection looking like this? Force all the record to go throught the reduce function?
{
"_id" : 80306,
"value" : {
"orders_count" : {
"total" : {
"count_1year" : 18,
"count_1month" : 6,
"count_7day" : 1
}
...
}
}

You can achieve this seamlessly with aggregation. Consider the following pipeline:
var dateSevenDaysAgo = new Date();
dateSevenDaysAgo.setDate(dateSevenDaysAgo.getDate()-7);
var dateMonthAgo = new Date();
dateMonthAgo.setMonth(dateMonthAgo.getMonth()-1);
var dateYearAgo = new Date();
dateYearAgo.setFullYear(dateYearAgo.getFullYear()-1);
var pipeline = [
{ "$match": { "$dt_order": { "$gte": dateYearAgo } } },
{
"$group": {
"_id": "$id_client",
"count_1year": {
"$sum": {
"$cond": [
{ "$gte": [ "$dt_order", dateYearAgo ] },
1, 0
]
}
},
"count_1month": {
"$sum": {
"$cond": [
{ "$gte": [ "$dt_order", dateMonthAgo ] },
1, 0
]
}
},
"count_7day": {
"$sum": {
"$cond": [
{ "$gte": [ "$dt_order", dateSevenDaysAgo ] },
1, 0
]
}
}
}
},
{ "$out": "tmp_indicators" }
];
db.orders.aggregate(pipeline);
db.tmp_indicators.find();

Found a solution using the finalize utility.
var mapOrders = function() {
var v_order = {
order_date : this.dt_order
...
};
emit(this.clientid, v_order);
};
var reduceOrders = function(p_clientid, p_orders) {
// Initialization of the output format of the couters
var r_result = { orders_count : {
total: {
1year: 0,
1month: 0,
7day: 0
}
...
}}
for (var c_order = 0; c_order < p_orders.length; c_order++) {
// Increment counters
}
return (r_result);
};
var finalizeOrders = function(p_clientid, p_ReducedDrders) {
if (typeof p_ReducedDrders.orders_count === 'undefined' )
// Initialization of the output format of the couters
var r_result = { orders_count : {
total: {
1year: 0,
1month: 0,
7day: 0
}
...
}}
// do the same stuff as the for loop in the reducer
}
else {
r_result = p_ReducedDrders
}
return (r_result);
};
db.orders.mapReduce(
mapOrders,
reduceOrders,
{
out: { merge: "tmp_orders_indicators" },
finalize : finalizeOrders
}
)

Related

MongoDB Find documents created in past 7 days and return 0 on days where no document was created

I'm trying to find documents from last 7 days in my Mongo Database.
I'm able to receive data in desired format where specific date and numOfTickets created on that given date are returned:
{
"datesUsed": {
"startDate": "2022-04-02T14:42:14.223Z",
"endDate": "2022-04-09T14:42:14.223Z"
},
"data": [
{
"date": "02/04/2022",
"numOfTickets": 1
},
{
"date": "03/04/2022",
"numOfTickets": 1
},
{
"date": "04/04/2022",
"numOfTickets": 2
},
{
"date": "06/04/2022",
"numOfTickets": 1
},
{
"date": "07/04/2022",
"numOfTickets": 1
},
{
"date": "08/04/2022",
"numOfTickets": 2
},
{
"date": "09/04/2022",
"numOfTickets": 1
}
]
}
The problem is that Mongo only returns data on days where a document was created, but returns nothing on days where no document was created - in this case 05/04/2022.
What I'm trying to achieve is to also include another JSON object that would say something in these terms:
{
"date": "05/04/2022",
"numOfTickets": 0
}
This is what I have so far:
const companyId = req.query.companyId;
let dates = [];
const data = [];
// Last 7 days
const endDate = new Date();
const startDate = new Date(Date.now() - 604800000);
// Find tickets from past 7 days
const allCompanyTickets = await ticketModel
.find({
company_id: companyId,
createdAt: { $gte: new Date(startDate), $lte: new Date(endDate) },
})
.sort({ createdAt: 1 });
console.log(allCompanyTickets)
// Push them to an array
allCompanyTickets.forEach((ticket) => {
dates.push(ticket.createdAt.toLocaleDateString());
});
// Count occurences of days in the dates array
function countOccurrences(arr) {
return arr.reduce(function (a, b) {
a[b] = a[b] + 1 || 1;
return a;
}, []);
}
// Make an object from the data above
const datesOrdered = countOccurrences(dates);
// Give the data above keys and push it to a new array
for (let key in datesOrdered) {
const tempObj = { date: key, numOfTickets: datesOrdered[key] };
data.push(tempObj);
}
res.status(200).json({ datesUsed: { startDate, endDate }, data: data });
Something like this
db.ticketModel.aggregate([
{
$set: {
datesUnused: {
$range: [ 0, { $dateDiff: { startDate: "$startDate", endDate: "$endDate", unit: "day" } } ]
}
}
},
{
$set: {
datesUnused: {
$map: {
input: "$datesUnused",
in: {
date: {
$dateAdd: {
startDate: { $dateTrunc: { date: "$datesUsed.startDate", unit: "day" },
unit: "day",
amount: "$$this"
}
},
numOfTickets: 0
}
}
}
}
},
{
$set: {
data: {
$filter: {
input: "$datesUnused",
cond: { $not: { $in: [ "$$this.date", "$data.date" ] } }
}
}
}
}
])
Mongo Playground

How to resort array objects by position when one of their positions changes?

Desired Behaviour
I am trying to update the position property of each object in an array of objects when one of their positions changes. For example, object with position 5 is moved to position 0 and therefore other objects' positions must also change.
I haven't been able to find an "array iterator" for MongoDB so have been trying to approach the problem from other angles but I think I am over-complicating it.
Schema
statements: [
{
"position": 0,
"id": "a"
},
{
"position": 1,
"id": "t"
},
{
"position": 2,
"id": "z"
},
{
"position": 3,
"id": "q"
},
{
"position": 4,
"id": "l"
},
{
"position": 5,
"id": "b"
}
]
Frontend Scenario
Figure A: Starting state of ordered divs
Figure B: Changed state of ordered divs (statement5has been moved to position0)
What I've Tried
Pseudo Code
I think the logic can be expressed as:
The objects position will change from the old_position to the
new_position by an arbitrary amount of places.
If the reposition_direction is backwards, all objects with position
greater than or equal to the new_position should increment (except for
the object being moved which should be assigned the new_position)
If the reposition_direction is forwards, all objects with position
less than or equal to the new_position should decrement (except for
the object being moved which should be assigned the new_position)
(update: this logic is incorrect, working logic added to answer)
This is what I have so far:
if (reposition_direction === "backwards") {
var new_filter = { _id: o_id, "statements.position": { $gte: new_position } };
var new_update = { $inc: { "statements.$[elem].position": 1 } };
var array_filters = { "arrayFilters": [{ "elem.position": { $gte: new_position } }], "multi": true };
} else if (reposition_direction === "forwards") {
var new_filter = { _id: o_id, "statements.position": { $lte: new_position } };
var new_update = { $inc: { "statements.$[elem].position": -1 } };
var array_filters = { "arrayFilters": [{ "elem.position": { $lte: new_position } }], "multi": true };
}
collection.updateOne(new_filter, new_update, array_filters, function(err, doc) {
if (err) {
res.send(err);
} else {
res.json({ response: "hurrah" });
}
});
Each time you change your array set the position on each of its members:
for (var i = 0; i < statements.length; i++) {
statements[i].position = i;
}
It was suggested the better idea was just to get the array, iterate over the result, and then set the value of the whole array, but I was intrigued to see if i could get the sorting logic right, and i think the following works. Just pasting below for reference.
var new_position = Number(request_body.new_position);
var old_position = Number(request_body.old_position);
var reposition_direction = request_body.reposition_direction;
var statement_id = request_body.statement_id;
var filter = { _id: o_id };
if (reposition_direction === "backwards") {
// if backwards, position filter is $gte new_position AND $lt old_position
var new_filter = { _id: o_id, "statements.position": { $gte: new_position, $lt: old_position } };
// increment
var new_update = { $inc: { "statements.$[elem].position": 1 } };
var array_filters = { "arrayFilters": [{ "elem.position": { $gte: new_position, $lt: old_position } }], "multi": true };
} else if (reposition_direction === "forwards") {
// if forwards, position filter is $lte new_position AND $gt old_position
var new_filter = { _id: o_id, "statements.position": { $lte: new_position, $gt: old_position } };
// decrement
var new_update = { $inc: { "statements.$[elem].position": -1 } };
var array_filters = { "arrayFilters": [{ "elem.position": { $lte: new_position, $gt: old_position } }], "multi": true };
}
collection.updateOne(new_filter, new_update, array_filters, function(err, doc) {
if (err) {
res.send(err);
} else {
// set the moved objects property to the new value
var new_filter = { _id: o_id, "statements.id": statement_id };
var new_update = { $set: { "statements.$.position": new_position } };
collection.findOneAndUpdate(new_filter, new_update, function(err, doc) {
if (err) {
res.send(err);
} else {
res.json({ response: "hurrah" });
}
});
}
});
The success handler call to findOneAndUpdate() was inspired by this answer:
https://stackoverflow.com/a/30387038
I tested the following combinations and they seem to work:
move 1 to 4
move 4 to 1
move 0 to 5
move 5 to 0
move 1 to 2
move 2 to 1
move 0 to 1
move 1 to 0
move 4 to 5
move 5 to 4

How to get one json object format from another JSON format

Maybe this question has already been asked and answered somewhere but after searching for more than 3 hrs I'm asking this question.
Below is my JSON data
var my_data = [
{
"TempRture_qc": 4,
"VoltAGE": 44.09722,
"TempRture": 22.32,
"VoltAGE_qc": 55,
"_time": "2018-08-07T03:39:29.001Z"
},
{
"TempRture_qc": 2,
"VoltAGE": 42.09722,
"TempRture": 22.12,
"VoltAGE_qc": 0,
"_time": "2018-08-07T03:39:30.006Z"
},
{
"TempRture_qc": 1,
"VoltAGE": 43.09722,
"TempRture": 22.82,
"VoltAGE_qc": 0,
"_time": "2018-08-07T03:39:31.009Z"
}
];
desired output i need
[
{
"name": "TempRture_qc",
"data": [
{"name":"2018-08-07T03:39:29.001Z","y":4},
{"name":"2018-08-07T03:39:30.006Z","y":2},
{"name":"2018-08-07T03:39:33.017Z","y":1}
]
},
{
"name": "VoltAGE",
"data": [
{"name":"2018-08-07T03:39:29.001Z","y":44.09722},
{"name":"2018-08-07T03:39:30.006Z","y":42.09722},
{"name":"2018-08-07T03:39:33.017Z","y":43.09722}
]
},
{
"name": "TempRture",
"data": [
{"name":"2018-08-07T03:39:29.001Z","y":22.32},
{"name":"2018-08-07T03:39:30.006Z","y":22.12},
{"name":"2018-08-07T03:39:33.017Z","y":22.82}
]
},
{
"name": "VoltAGE_qc",
"data": [
{"name":"2018-08-07T03:39:29.001Z","y":55},
{"name":"2018-08-07T03:39:30.006Z","y":0},
{"name":"2018-08-07T03:39:33.017Z","y":0}
]
}
]
for getting this above output i have tried below code.
var accounting = [];
var fieldName = {};
for (var x in obj){
var mykey = Object.keys(obj[x]);
for (var mk in mykey){
if(mykey[mk]=='VoltAGE'){
fieldName.name = mykey[mk];
// accounting.push({
// "name":mykey[mk]
// })
}
if(mykey[mk]=='TempRture'){
fieldName.name = mykey[mk];
}
// console.log(mykey[mk]); //to get the key name
}
accounting.push({
"name" : obj[x]._time,
"y" : obj[x][employees.name],
})
fieldName.data = accounting;
}
console.log(fieldName );
by doing this what I'm getting is below JSON
{ name: 'TempRture',
data:
[ { name: '2018-08-07T03:39:29.001Z', y: 22.32 },
{ name: '2018-08-07T03:39:32.014Z', y: 22.12 },
{ name: '2018-08-07T03:39:33.017Z', y: 22.82 } ] }
I'm not able to understand how I will get the data in one JSON object.
For a solution with low time complexity, try .reduceing into an object indexed by keys of the inner object, creating a { name, data: [] } at that key in the accumulator if it doesn't exist there yet. Then, push to the data array, and get the values of the whole object:
var my_data=[{"TempRture_qc":4,"VoltAGE":44.09722,"TempRture":22.32,"VoltAGE_qc":55,"_time":"2018-08-07T03:39:29.001Z"},{"TempRture_qc":2,"VoltAGE":42.09722,"TempRture":22.12,"VoltAGE_qc":0,"_time":"2018-08-07T03:39:30.006Z"},{"TempRture_qc":1,"VoltAGE":43.09722,"TempRture":22.82,"VoltAGE_qc":0,"_time":"2018-08-07T03:39:31.009Z"}]
console.log(Object.values(
my_data.reduce((a, { _time, ...obj }) => {
Object.entries(obj).forEach(([name, val]) => {
if (!a[name]) a[name] = { name, data: [] };
a[name].data.push({ name: _time, y: val });
});
return a;
}, {})
));
var my_data=[{"TempRture_qc":4,"VoltAGE":44.09722,"TempRture":22.32,"VoltAGE_qc":55,"_time":"2018-08-07T03:39:29.001Z"},{"TempRture_qc":2,"VoltAGE":42.09722,"TempRture":22.12,"VoltAGE_qc":0,"_time":"2018-08-07T03:39:30.006Z"},{"TempRture_qc":1,"VoltAGE":43.09722,"TempRture":22.82,"VoltAGE_qc":0,"_time":"2018-08-07T03:39:31.009Z"}]
var keys = Object.keys(my_data[0])
var result= [];
for(i = 0; i<keys.length-1; i++) {
var obj = {name: keys[i],data: []}
obj.data = my_data.map(val=>({name: val["_time"], y: val[keys[i]]}));
result.push(obj);
}
console.log(result)
An understandable answer with map, findIndex and forEach functions will be
var my_data = [{ "TempRture_qc": 4, "VoltAGE": 44.09722, "TempRture": 22.32, "VoltAGE_qc": 55, "_time": "2018-08-07T03:39:29.001Z" }, { "TempRture_qc": 2, "VoltAGE": 42.09722, "TempRture": 22.12, "VoltAGE_qc": 0, "_time": "2018-08-07T03:39:30.006Z" }, { "TempRture_qc": 1, "VoltAGE": 43.09722, "TempRture": 22.82, "VoltAGE_qc": 0, "_time": "2018-08-07T03:39:31.009Z" } ],
result = [];
my_data.map(itm => {
let keys = Object.keys(itm);
keys.forEach(iitt => {
if (iitt != '_time') {
let index = result.findIndex(ii => {
return ii.name == iitt;
})
if (index == -1) {
result.push({
name: iitt,
data: []
});
result[result.length - 1].data.push({
name: itm["_time"],
y: itm[iitt]
})
} else {
result[index].data.push({
name: itm["_time"],
y: itm[iitt]
});
}
}
})
})
console.log(result)

Sort data 'Abr/2017'

I need to sort my array, he is like this:
x = {
'Abr/2017': [ { id: 1 } ],
'Fev/2018': [ { id: 1 } ],
'Jul/2017': [ { id: 1 } ],
'Abr/2018': [ { id: 1 } ],
'Fev/2017': [ { id: 1 } ],
'Jul/2018': [ { id: 1 } ],
'Dez/2019': [ { id: 1 } ]
}
and I need him to be sorted first in the year and then in the months
Because your data is represented as object, you have to convert it to array as a first step. Then you can sort. Later you can map result array to whatever you want.
Important thing is that if you want to keep this data in some order then you have to use array structure
let x = {
'Abr/2017': [ { id: 1 } ],
'Fev/2018': [ { id: 1 } ],
'Jul/2017': [ { id: 1 } ],
'Abr/2018': [ { id: 1 } ],
'Fev/2017': [ { id: 1 } ],
'Jul/2018': [ { id: 1 } ],
'Dez/2019': [ { id: 1 } ]
}
// this function transform your object to array representation
// where your actual key (e.g. Fev/2017) is stored in helper
// property called key
function toArray(obj) {
return Object.keys(obj).reduce((arr, key) => {
arr.push({ key, data: obj[key] })
return arr
}, [])
}
function byYearAndMonth() {
// month definition - help with sorting
let monthOrder = {
'Jan': 1,
'Fev': 2,
'Mar': 3,
'Abr': 4,
'Mai': 5,
'Jun': 6,
'Jul': 7,
'Ago': 8,
'Set': 9,
'Out': 10,
'Nov': 11,
'Dez': 12
}
let mapDate = function([month, year]) {
return [monthOrder[month], Number(year)]
}
// actual sorting function
return function(a, b) {
const [aMonth, aYear] = mapDate(a.key.split('/'))
const [bMonth, bYear] = mapDate(b.key.split('/'))
if(aYear < bYear) {
return -1
}
if(aYear > bYear) {
return 1
}
if(aMonth < bMonth) {
return -1
}
if(aMonth > bMonth) {
return 1
}
return 0
}
}
// lets try how it works
let xArray = toArray(x)
xArray.sort(byYearAndMonth());
var result = xArray.map(x => x)
// with map (or reduce) you can transform it to whatever you want
// I'm just returning exactly the same object
console.log(result)

Add new attribute to JSON?

I have JSON data and I want to update items on it.
How can I add a name attribute to ALL id's in controller ?
{
"games" : [
{ "id":["1"] },
{ "id":["2"] },
{ "id":["3"] },
{ "id":["4"] },
{ "id":["5"] },
{ "id":["6"] }
]
}
Should be :
{
"games" : [
{ "id":["1"],"name":"1" },
{ "id":["2"],"name":"2" },
{ "id":["3"],"name":"3" },
{ "id":["4"],"name":"4" },
{ "id":["5"],"name":"5" },
{ "id":["6"],"name":"6" }
]
}
for (var i = 1; i <= games.length; i++) {
games[].name = i;
}
Use forEach to loop through every items of the data.games array and then simply add the name property using game.name = game.id[0].
const data = {
"games" : [
{ "id":["1"] },
{ "id":["2"] },
{ "id":["3"] },
{ "id":["4"] },
{ "id":["5"] },
{ "id":["6"] }
]
};
data.games.forEach(game => game.name = game.id[0]);
console.log(data);

Categories

Resources