So, I'm making a school project and I've made a system to give points, and store names and details of classmates, now the part, where I'm stuck, in that I want to be able to make a leaderboard that lists names and points from highest to lowest how would I do that
The below query will list the users from score highest to lowest
Class.find({Score: { $gt: 0}}).sort({ score: -1 }).exec( function(err, class){
message.channel.send(`Leaderboard\n ${class.FirstName}`)
})
Given Users your models in which are stored names, details and scores of your classmates, here're some solutions to sort based on a specified field (in this case, score), using the Mongoose's model built-in sort function:
Users.find({ Score: > 0 }).sort({ Score: 'asc' }).exec((err, docs) => { ... });
// Another way...
Users.find({ Score: > 0 }).sort({ Score: 'ascending' }).exec((err, docs) => { ... });
// Yet, another way...
Users.find({ Score: > 0 }).sort({ Score: 1 }).exec((err, docs) => { ... });
Check out the documentation for more.
Related
Based on a certain time interval I need to implement pre-aggregated statistical data based on the following model:
I have a Product entity and ProductGroup entity that plays a role of Products container. I can have 0..N Products and 0..N ProductGroups with the MANY_2_MANY relationship between Products and ProductGroups.
Based on some own business logic I can calculate the order of every Product in the every ProductGroup.
I will do this calculation continuously per some period of time... let's say via Cron job.
I also would like to store the history for every calculation(versions) in order to be able to analyze the Product positions shifts.
I have created a simple picture with this structure:
Right now I use MongoDB database and really interested to implement this structure on MongoDB without introducing new technologies.
My functional requirements - I need to have the ability to quickly get the position(and position offset) for certain Product in the certain ProductGroup. Let's say P2 position and offset for ProductGroup1. The output should be:
position: 1
offset : +2
Also, I'd like to visualize the graphics and show the historical changes of positions for a certain Product with a certain ProductGroup. For example for Product P2 in ProductGroup1 the output should be:
1(+2), 3(-3), 0
Is it possible to implement with MongoDB and if so, could you please describe the MongoDB collection(s) structure in order to support this?
Since the only limitation is to "quickly query the data as I described at my question", the simplest way is to have a collection of snapshots with an array of products:
db.snapshots.insert({
group: "group 1",
products:[
{id:"P2", position:0, offset:0},
{id:"P4", position:1, offset:0},
{id:"P5", position:2, offset:0},
{id:"P6", position:3, offset:0}
], ver:0
});
db.snapshots.insert({
group: "group 1",
products:[
{id:"P3", position:0, offset:0},
{id:"P5", position:1, offset:1},
{id:"P1", position:2, offset:0},
{id:"P2", position:3, offset:-3},
{id:"P4", position:4, offset:0}
], ver:1
});
The index would be
db.snapshots.createIndex(
{ group: 1, ver: -1, "products.id": 1 },
{ unique: true, partialFilterExpression: { "products.id": { $exists: true } } }
);
And the query to fetch current position of a product in the group ("P4" in "group 1" in the example):
db.snapshots.find(
{ group: "group 1" },
{ _id: 0, products: { $elemMatch: { id: "P4" } } }
).sort( { ver:-1 } ).limit(1)
A query to fetch historical data is almost the same:
db.snapshots.find(
{ group: "group 1" },
{ _id: 0, products: { $elemMatch: {id: "P4" } }, ver: 1 }
).sort({ver:-1})
I have a collection of videos :
var VideoSchema = new mongoose.Schema({
url: {
type: String, required : true
},
orderBy: {
type: Number
},
views: [{
type: String
}]
});
'orderBy' lets me define the order in which I serve videos
'views' is a list of usernames, those who have watched the video.
I want to keep users from watching again the same video until they have watched all videos in the collection. So I keep the names of users who have watched the video inside the 'views'.
Now I query for videos using 'sort'.
return this.find(videoQuery)
.skip(offset)
.limit(count || 10) // #todo 10 : param
.sort({views: {$meta: username}, 'orderBy': -1})
.exec();
And I get the error message :
Can't canonicalize query: BadValue bad sort specification
Can you help ?
May be wrong but from mongo docs. When you specify $meta in sort you need to include it in projectedFieldName.
https://docs.mongodb.com/manual/reference/operator/projection/meta/#proj._S_meta
So it should looks something like this
return this.find(videoQuery, { views: { $meta: 'textScore' } })
.skip(offset)
.limit(count || 10) // #todo 10 : param
.sort({views: {$meta: textScore}, 'orderBy': -1})
.exec();
UPDATED
Seems that $meta only accepts 'textScore' as #Pierre mentioned.
So another suggestion is to use underscore or native js to sort data when response comes from DB. This will be less efficient and may need some adjustments, but this will work definitely.
Hope this helps.
Like Facebook, I would like to aggregate the results. But I can't figure out how to go about it.
Example:
Let's say 10 users like my posts.
I don't want to get 10 notifications. 1 is of course enough.
This is my schema:
var eventLogSchema = mongoose.Schema({
//i.e. Somebody commented, sombody liked, etc.
event: String,
//to a comment, to a post, to a profile, etc.
toWhat: String,
//who is the user we need to notify?
toWho: {type: mongoose.Schema.Types.ObjectId, ref:'User'},
//post id, comment id, whatever..
refID: {type: mongoose.Schema.Types.ObjectId},
//who initiated the event.
whoDid: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
// when the event happened
date: {type:Date, default: Date.now()},
//whether the user already saw this notification or not.
seen: {type:Boolean, default: false}
})
so I need to count the times
Ex.1: event='liked' and toWhat="post" and refID=myPostID and seen=false
But at the same time, I would like to populate the last event with this parameters on the 'who' path so I could display "Michael and 9 other people liked your post(link to post)"
Every way I can think of doing this is clunky and requires multiple queries that feel like they would cost a lot of system resources and I am wondering if there's a simple way to do it.
Actually it gets more complicated then that.
I do not want to specify values like I did in Ex.1.
Instead I would like to say
aggregate all events with similar 'event', 'toWhat',
'refID' with value seen=false and populate the last one on the 'who' path.
Would love some reading materials, links, advice, or anything.
Thanks!
Managed to solve it like this.
Not sure if it's optimal, but it works.
//The name of my Schema
Notification.aggregate([
{
$match: {
//I only want to display new notifications
seen: {$ne: true}
//to query a specific user add
// toWho: UserID
}
},
{
$group: {
//groups when event, toWhat, and refID are similar
_id: {
event: '$event',
toWhat: '$toWhat',
refID: '$refID',
},
//gets the total number of this type of notification
howMany: {$sum: 1},
//gets the date of the last document in this query
date: {$max: '$date'},
//pulls the user ID of the last user in this query
user: {$last: '$whoDid'}
}
}
]).exec(function (err, results) {
if (err) throw err;
if (results) {
//after I get the results, I want to populate my user to get his name.
Notification.populate(results, {path: 'user', model: "User"}, function (err, notifications) {
if (err) throw err;
if (notifications) res.send(notifications);
})
}
})
I'm not sure whether it's possible to populate the aggregated result in one query, I assume that if it's possible it would be optimal, but so far, this seems acceptable for my needs.
Hope this helps.
I am using Mongoose to fetch data from MongoDB. Here is my model.
var EmployeeSchema = new Schema({
name: String,
viewCount: { type: Number, default: 0 },
description: {
type: String,
default: 'No description'
},
departments: []
});
I need to find top 5 employees where count(viewCount) is highest order by name.
I am thinking of finding all the employee by using find() & then read viewCount property & produce the result. is there any better way to get the desired result.
All you need here is .sort() and .limit():
Employee.find().sort({ "viewCount": -1, "name": 1 }).limit(5)
.exec(function(err,results) {
});
And that is the top 5 employees in views ordered by name after the viewCount.
If you want them ordered by "name" in your final five, then just sort that result:
Employee.find().sort({ "viewCount": -1, "name": 1 }).limit(5)
.exec(function(err,results) {
// sort it by name
results.sort(function(a,b) {
return a.name.localeCompare(b.name);
});
// do something with results
});
You can sort by the view count and limit the search results to 5.
In code it might look like this:
Employee
.find()
.sort([['viewCount',-1], ['name',-1]])
.limit(5)
.exec(function(err, results){
//do something with the results here
});
I'm looking at keeping track of scores/points per user. From examples I've seen, it seems normal to just keep track of a total count of something in a field. However, I'm concerned about being able to backtrack or keep track of the scores/points given to the user in case of cheating. Here's what I've got in mind:
Meteor.User Collection:
Meteor.user.profile: {
...
totalScore: 0
...
}
Scenario 1:
Just add total score and keep track of it per user:
updateScore() {
var currentUser = Meteor.user();
currentUser.profile.update({ _id: this._id }{$inc: { totalScore: 1} });
}
Scenario 1:
Put score into separate Collection first to log it, before adding to total score of user:
Scores Collection:
Scores: {
playerId: ,
score: ,
...
}
updateScore() {
var currentUser = Meteor.user();
Scores.insert({ playerId: this._id, score: 1, ...});
currentUser.profile.update({ _id: this._id }{$inc: { totalScore: 1} });
//if not the above, then thisor this
var currentUserScore = Calculate total score count from Scores collection of current user here
Meteor.user.profile.update({ _id: this._id }{$set: { totalScore: currentUserScore} });
}
So what I'd like to know is, does Scenario 2 make sense vs. Scenario 1? And if Scenario 2 makes sense, if I calculate for the total score via the variable currentUserScore then use that to update the user's totalScore profile field (this runs every time the score needs to be updated), will this be detrimental to the app's performance?
Based on our discussion, Scenario 2 makes the most sense to me, especially given that the score history may have value outside of auditing the total. Keep in mind it's always easier to remove data than it is to create it, so even if the history doesn't prove useful there is no harm in removing the collection sometime later.
I would implement an addScore method like this:
Meteor.methods({
addScore: function(score) {
check(score, Number);
Meteor.users.update(this.userId, {
$inc: {'profile.totalScore': score}
});
Scores.insert({
playerId: this.userId,
score: score,
createdAt: new Date()
});
}
});
Unless you can think of a compelling reason to do so, I suspect the db/computation overhead of aggregating the totalScore each time would not be worthwile. Doing so only fixes the case where a user cheated by updating her profile directly. You can solve that by adding the following:
Meteor.users.deny({
update: function() {
return true;
}
});
I'd recommend adding the above regardless of the solution you go with as user profiles can be updated directly by the user even if insecure has been removed. See this section of the docs for more details.
Finally, if you want to audit the totalScore for each user you can aggregate the totals as part of a nightly process rather than each time a new score is added. You can do this on the server by fetching the Scores documents, or directly in mongodb with aggregation. Note the latter would require you to use a process outside of meteor (my understanding is that the aggregation packages for meteor don't currently work but you may want to research that yourself).