How to find data in for every day of week in mongoose node js - javascript

Query
const weekGraph = await tmUserSubscriptions.aggregate([
{
$match:{$and:[{subscriptionId: mongoose.Types.ObjectId(subscriptionId)},
{createdAt:{$gte:moment().startOf('isoweek').toDate(),
$lt:moment().endOf('isoweek').toDate()}}
]}
},
{"$project":{
"_id:":1,
"createdAt":{"$dayOfWeek":"$createdAt"},
"subscriptionId":1,
}},
{"$group":{
"_id":"$createdAt",
"count":{$sum:1},
}}
])
Result i get
"data": [
{
"_id": 7,
"count": 1
},
{
"_id": 5,
"count": 2
},
{
"_id": 6,
"count": 1
}
]
expected Result
"data": [
{
"_id": 7,
"count": 1
},
{
"_id": 6,
"count": 2
},
{
"_id": 5,
"count": 1
},
{
"_id": 4,
"count": 0
},{
"_id": 3,
"count": 0
},{
"_id": 2,
"count": 0
}{
"_id": 1,
"count": 0
}
]
So here i want to achieve all data of current week day by day, in my current query if there is no data any of week day then it will not return that day, but as per my expected result i want all day of week data, if there is no data for any of week day then it will return 0, so i want all 7 days data,
here _id is represent day of week

Mongoose/MongoDB will only return the aggregate if the key exists. Otherwise, it will not return you the data (less data to transfer through the connection is always faster). Therefore, you will need to provide your own defaults if the aggregate does not have data for you.
var results = [{ _id: 1, count: 1 }] // assumed from your response
var hasResult = []
for (var result of results) {
hasResult.push(result._id)
}
for (var i = 1; i <= 7; i++) {
if (!hasResult.includes(i)) {
results.push({ _id: i, count: 0 })
}
}
console.log(results)

Related

Is there a way to count distinct values in mongoose with specific cut off date

For example, I have the following two docs
[
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T15:11:21.327Z"
"user_id": "2",
},
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"_id": "5fc534595144dd0030c44f95",
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
}
]
the results should be
[
{
"date": "2020-12-13",
"count":1
},
{
"date": "2020-12-14",
"count":2
}
]
where the count is the number of distinct docs via user_ids till the date that specific cut off date
given data
data=[
{
"createdAt": "2020-12-14T15:11:21.327Z",
"user_id": "2",
},
{
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
},{
"createdAt": new Date("2020-12-14T14:10:58.027Z"),
}
]
> db.dummy.insert(data)
You may use aggregate: use $group with _id being the date's day in conjunction with $sum)
> db.dummy.aggregate({$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}})
{ "_id" : "2020-12-14", "count" : 3 }
{ "_id" : "2020-12-13", "count" : 1 }
edit: mongoose wise same may hold
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/dummy')
const UDate = mongoose.model('X', { createdAt:String, user_id: String }, 'dummy')
;(async()=>{
mongoose.set('debug', true)
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}}
const v = await UDate.aggregate([group])
console.log('s : ', JSON.stringify(v))
mongoose.disconnect()
})()
edit2: to handle unicity of userIds so there are not counted twice per date, you may use $addToSet instead of sum followed by a projection using $size
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, userIds:{$addToSet:'$user_id'}}}
const count = {$project:{date:'$_id', count: {$size: '$userIds'}} }
const v = await Stock.aggregate([group, count])
Lastly, if you feel always more, you can "rename" the _id field as date during the projection
{$project:{date:'$_id', _id:0, count: {$size: '$userIds'}} }
$gorup by createdAt date after getting substring using $substr and make array of unique user ids on the base of $addToset
get total element in count array using $size
db.collection.aggregate([
{
$group: {
_id: { $substr: ["$createdAt", 0, 10] },
count: { $addToSet: "$user_id" }
}
},
{ $addFields: { count: { $size: "$count" } } }
])
Playground

Sort by the sum of array in object

I am looking for a solution to sort an array by the sum of an array property within an object.
For example if the main array is
[
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
]
How can I sort the sum of Day to return as
[
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
},
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
}
]
You just need sort your array with comparator, that uses reduce to calc sum of inner array values:
let arr = [{"Grid": {"Day": [11,12]}, "Name": "One"},
{"Grid": {"Day": [5,2]}, "Name": "Two"},
{"Grid": {"Day": [1,2]}, "Name": "Two"}];
let sum = el => el.Grid.Day.reduce((a,b) => a + b);
arr.sort((a,b) => sum(a) - sum(b));
console.log(arr)
You can use a combination of reduce to sum the array, and sort to order the output:
var input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
var result = input.sort( (a,b) => sumOfDay(a) - sumOfDay(b));
console.log(result);
function sumOfDay(obj){
return obj.Grid.Day.reduce( (acc,curr) => acc + curr, 0);
}
Note that Array.prototype.sort actually mutates the original array in place. so the above could also do
input.sort( (a,b) => sumOfDay(a) - sumOfDay(b));
console.log(input);
So, don't fall into the trap of thinking the original array is unchanged just because I assigned the result to result!.
If you do wish to sort a copy of the array do this:
var result = input.slice().sort( (a,b) => sumOfDay(a) - sumOfDay(b));
Create a new Array of a by mapping through it and using reduce on the Day Array of Grid to get your sum which you can compare within a sort to return your list sorted by summed days.
const a = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
]
const daySum = ({Grid}) => Grid.Day.reduce((prev, curr) => prev+curr, 0)
const sorted = [...a].sort(daySum)
console.log(sorted)
console.log(a) //Original array intact
Just "another" approach to solve the issue: assuming you (someday, later, eventually) may need to sort again, a good approach may also be to add a property to each grid item holding the sum of the days, avoiding the .reduce call every time you need to sort the array.
In this approach, .forEach is used to create the new property (through .reduce), and then .sort is used to sort the array in-place.
const input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
// Add a DaySum property evaluating the sum of the days.
input.forEach(i => i.Grid.DaySum = i.Grid.Day.reduce((a,b) => a + b));
// ^--- the second parameter (initial value) is unneeded here due to the fact that all elements are actually numeric, hence if the initial value is the first element of the array, which is a number already.
// Sor the array by that property.
input.sort((a,b) => a.Grid.DaySum - b.Grid.DaySum);
console.log(input);
Or, as suggested by #Andreas below, you can directly assign the property while sorting:
const input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
const sum = (a,b) => a + b;
input.sort((a,b) => {
a.Grid.DaySum = a.Grid.DaySum || a.Grid.Day.reduce(sum);
b.Grid.DaySum = b.Grid.DaySum || b.Grid.Day.reduce(sum);
return a.Grid.DaySum - b.Grid.DaySum;
});
console.log(input);

Return all values as an array after the map function

I want to write to the array the value of each sum of all the reports.
month: donate_report.report[0].reports[0].sum
Unfortunately, this function returns an empty array:
month: donate_report.report[0].reports;
const doubles = month.map(function (elem) {
return elem.sum;
});
Could you please tell me what am I doing wrong? thanks in advance
"reports": [
{
"id": 1,
"sum": 5221,
},
{
"id": 2,
"sum": 5421,
}
]
The data structure you provided was not complete or wrong. Considering it as a object you can use map and return the sum property
var a={"reports": [
{
"id": 1,
"sum": 5221,
},
{
"id": 2,
"sum": 5421,
}
]}
const doubles=a.reports.map((e)=>e.sum);
console.log(doubles)

Sort array by child's child

The situation is: I have an array of objects, in which every object has an array of objects. The array looks like this:
[
{
"dislikes": [
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511001,
"timezoneOffset": -60,
"year": 118
},
},
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511008,
"timezoneOffset": -60,
"year": 118
},
}
],
},
{
"dislikes": [
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511011,
"timezoneOffset": -60,
"year": 118
},
},
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511028,
"timezoneOffset": -60,
"year": 118
},
}
],
}
]
So I want to sort the users, and the dislikes by the time in their dislikes. So the user with the earliest dislike would be first, as well as the earliest dislike would be first in each users' dislikes array. I believe I have to do multiple sorts, but how can I do that exactly?
You can map the items and add a property to it containing the earliest dislike and then sort on that:
const data = [{"dislikes":[{"createDate":{"date":11,"day":0,"hours":18,"minutes":15,"month":10,"seconds":11,"time":1541956511001,"timezoneOffset":-60,"year":118}},{"createDate":{"date":11,"day":0,"hours":18,"minutes":15,"month":10,"seconds":11,"time":1541956511008,"timezoneOffset":-60,"year":118}}]},{"dislikes":[{"createDate":{"date":11,"day":0,"hours":18,"minutes":15,"month":10,"seconds":11,"time":1541956511011,"timezoneOffset":-60,"year":118}},{"createDate":{"date":11,"day":0,"hours":18,"minutes":15,"month":10,"seconds":11,"time":1541956511028,"timezoneOffset":-60,"year":118}}]}];
console.log(
data
//map and add newestDislike property
.map((d) => ({
...d,
//reduce and only takes the lowest time value
newestDislike: (d.dislikes || []).reduce(
(result, item) =>
item.createDate.time < result
? item.createDate.time
: result,
Infinity, //defaults to infinity (if no dislikes)
),
}))
.sort((a, b) => a.newestDislike - b.newestDislike),
);
If the dislikes in the user are already sorted by oldest date first then you can skip the map and reduce part. If a user can have empty dislikes or undefined then make sure you use a getter function with a default so your code won't crash:
//gets a nested prop from object or returns defaultValue
const get = (o = {}, path, defaultValue) => {
const recur = (o, path, defaultValue) => {
if (o === undefined) return defaultValue;
if (path.length === 0) return o;
if (!(path[0] in o)) return defaultValue;
return recur(o[path[0]], path.slice(1), defaultValue);
};
return recur(o, path, defaultValue);
};
console.log(
data.sort(
(a, b) =>
get(
a,
['dislikes', 0, 'createDate', 'time'],
Infinity,
) -
get(
b,
['dislikes', 0, 'createDate', 'time'],
Infinity,
),
),
);
//Supply the array you've metioned as the argument users to the below method, sortDislikesForAllUsers
let sortDislikesForAllUsers = function(users) {
return users.map(user => {
return {
dislikes: user.dislikes.sort((dislikeA, dislikeB) => ((dislikeA.createDate.time < dislikeB.createDate.time) ? -1 : (dislikeA.createDate.time > dislikeB.createDate.time) ? 1 : 0))
}
})
}
//Supply the array returned in the above method as input to the below method, sortUsers
let sortUsers = function(arrayOfSortedDislikesPerUser) {
return arrayOfSortedDislikesPerUser.sort((userA, userB) => ((userA.dislikes[0].createDate.time < userB.dislikes[0].createDate.time) ? -1 : (userA.dislikes[0].createDate.time > userB.dislikes[0].createDate.time) ? 1 : 0))
}
let arrayOfSortedDislikesPerUser = sortDislikesForAllUsers(users);
let finalSortedArray = sortUsers(arrayOfSortedDislikesPerUser);
console.log(finalSortedArray);
In the below snippet,
sortDislikesForAllUsers This method sorts the dislikes for individual
users
sortUsers This method sorts the users based on the first dislike time
of the sorted dislikes array obtained from the above method
Simple :)
Run the below snippet. You can directly copy paste it in your code!
let users = [{
"dislikes": [
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511001,
"timezoneOffset": -60,
"year": 118
},
},
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511008,
"timezoneOffset": -60,
"year": 118
},
}
],
},
{
"dislikes": [
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511011,
"timezoneOffset": -60,
"year": 118
},
},
{
"createDate": {
"date": 11,
"day": 0,
"hours": 18,
"minutes": 15,
"month": 10,
"seconds": 11,
"time": 1541956511028,
"timezoneOffset": -60,
"year": 118
},
}
],
}]
let sortDislikesForAllUsers = function(users) {
return users.map(user => {
return {
dislikes: user.dislikes.sort((dislikeA, dislikeB) => ((dislikeA.createDate.time < dislikeB.createDate.time) ? -1 : (dislikeA.createDate.time > dislikeB.createDate.time) ? 1 : 0))
}
})
}
let sortUsers = function(arrayOfSortedDislikesPerUser) {
return arrayOfSortedDislikesPerUser.sort((userA, userB) => ((userA.dislikes[0].createDate.time < userB.dislikes[0].createDate.time) ? -1 : (userA.dislikes[0].createDate.time > userB.dislikes[0].createDate.time) ? 1 : 0))
}
let arrayOfSortedDislikesPerUser = sortDislikesForAllUsers(users);
let finalSortedArray = sortUsers(arrayOfSortedDislikesPerUser);
console.log(finalSortedArray);
EDIT: WRT to the comment by #HMR:
1. It mutates the original array. Yes. If you want to avoid mutation, you must create a copy of the sent array.
let noRefCopy = new Array()
noRefCopy = noRefCopy.concat(originalArr)
Now, perform sorting on the copy and return the same.
2. If you wanna have checks for undefined etc, sure you can.
The above answer attempts to address the logic. Sure we can address the above 2 concerns if the question is really specific to them.
Cheers,
Kruthika
Check the code below. This will let you sort based on time:
function sortByTime(obj1, obj2){
return obj1.time - obj2.time;
}
array.sort((obj1, obj2)=>{
obj1.dislikes.sort(sortByTime);
obj2.dislikes.sort(sortByTime);
return obj1.dislikes[0].time - obj2.dislikes[0].time;
});
I did not get what you meant by earliest time. The above code sorts time in ascending order.
NOTE: The above code does not handle edge cases where a property night be missing
Something like as follows (with lodash.js)
_.each(users, (u) => { u.dislikes = _.sortBy(u.dislikes, 'createdDate.time'); });
users = _.sortBy(users, 'dislikes[0].createdDate.time');

Sort through an array, and push duplicates into a new array

I have an array of objects with dates. What I want to do is select all objects with the same date value and push those into a new array.
here's my code.
var posts = [
{
"userid": 1,
"rating": 4,
"mood": "happy",
"date": "2017-05-24T04:00:00.000Z"
},
{
"userid": 1,
"rating": 3,
"mood": "happy",
"date": "2017-05-24T04:00:00.000Z"
},
{
"userid": 1,
"rating": 3,
"mood": "angry",
"date": "2017-05-25T04:00:00.000Z"
},
{
"userid": 1,
"rating": 5,
"mood": "hungry",
"date": "2017-05-25T04:00:00.000Z"
}]
var may25=[];
for(i=0;i < posts.length;i++){
if(posts[i].date === posts[i].date){
may25.push(posts[i].date)
}
}
You could slice the date from the string and take it as key for an object for grouping items by date. The result is an object with all grouped objects.
var posts = [{ userid: 1, rating: 4, mood: "happy", date: "2017-05-24T04:00:00.000Z" }, { userid: 1, rating: 3, mood: "happy", date: "2017-05-24T04:00:00.000Z" }, { userid: 1, rating: 3, mood: "angry", date: "2017-05-25T04:00:00.000Z" }, { userid: 1, rating: 5, mood: "hungry", date: "2017-05-25T04:00:00.000Z" }],
groups = Object.create(null);
posts.forEach(function (o) {
var date = o.date.slice(0, 10);
groups[date] = groups[date] || [];
groups[date].push(o)
});
console.log(groups);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you are just looking for may 25 dates (as your question suggests):
var may25=[];
for (i=0;i < posts.length;i++) {
if (posts[i].date === "2017-05-25T04:00:00.000Z") {
may25.push(posts[i].date)
}
}
You may want to create an object that contains date => event arrays :
var result=posts.reduce((obj,event)=>((obj[event.date]=obj[event.date] || []).push(event),obj),{});
Now you can do:
result["2017-05-25T04:00:00.000Z"].forEach(console.log);
How it works:
posts.reduce((obj,event)=>...,{}) //iterate over the posts and pass each as event to the function and also pass an object to be filled
(obj[event.date]=obj[event.date] || [])//return the date array, or create a new one if it doesnt exist
.push(event)//append our event to it

Categories

Resources