Creating "team" sub-collections for users - node.js

Let's say there's a user collection.
The relevant information for this question from that collection is:
username: string,
teamid: Number,
totalscore: Number
Now for the final output it should list the teams, list the users under their teams using the teamid, and adding up the totalscore of each user to calculate the teamscore.
However, I can't come up with any way to do that.
I was looking into aggregation for this but eh, it's a bit too far above my hat.
The output for this api is rendered in json using express and mongoose.

You can use the following aggregation query.
Use a group stage with $push to get the player's name and $sum to get the sum of totalscore for a team.
db.coll.aggregate([
{'$group': {
'_id': '$teamid',
'totalscore': {'$sum': '$totalscore'},
'listofplayers': {'$push':'$username'}
}
}])

Related

Get number of products from each category in mongodb database

I'm new to mongodb and to overall databases side of development.
I'm trying to make a product listing site where all the categories would be displayed with the number of products within that particular category and when clicked on a particular category, it would get me all the products in that category.
Some things to note are:
every product will have only one category
each category will have multiple products
I don't know how to go about this problem and tried searching it online but couldn't exactly find what I was looking for. I've also tried making the schema for this but I do not know if it's the right approach or not and this is how it looks:
const productsSchema = {
category: String,
name: String,
price: String,
description: String,
thumbnail: String,
};
Side note: I'm using MERN stack.(if its of any help)
If I've understand well your question, you can use something like this:
db.collection.aggregate([
{
"$match": {
"category": "category1"
}
},
{
"$count": "total"
}
])
With this query you will get the total $count for each category.
Example here
In your frontend you will need a call for every category.
Maybe if your DB has a lot of different categories this is not a good approach, but if the number is not large enough you can call this query a couple times and you will get the result you want.
MongoDB Documentation reference here
I would say you should have a product schema and a product category schema, where the product category schema has an array of product ids that belong to that category.
In the product schema, you could also have a pointer to the category object that a product is linked to (as opposed to just the name of the category as a string).
Maybe take a look at mongoose populate https://mongoosejs.com/docs/populate.html

Adding value to already declared MongoDB object with mongoose Schema

I am new to MongoDB and mongoose. I am trying to create a Node & MongoDB auction app. So since it is actually an online auction, users should be able to bid for items. I successfully completed the user registration, sign in page and authentication process, however, I am a bit stuck in the bidding page.
I created a Schema using mongoose and each item for auction is saved in the database. I want to add name and price of each user who bid for the item in the same object in MongoDB, like this:
{
name: "valuable vase from 1700s",
owner: "John Doe",
itemId: 100029,
bids: {
100032: 30000,
100084: 34000
}
}
So each user will have ids like 100032: 30000, and when they bid, their "account id: price" will be added under bids in the database object of the item.
I made some research and found some ways to solve the problem but I want to know if what I want to do is possible and if it is the right solution to do.
Thanks for giving me your time!
There are indeed couple of ways to achieve what you want.
In my opinion, a collection called ItemBids, where each document includes this data structure, will benefit you the most.
{
itemId: ObjectId # reference to the item document
accountId: ObjectId # reference to the account
bid: Number # the bid value
}
This pattern is suitable for your case because you can easily query the bids by whatever you want -
You can get all the account bids, you can get all the item bids, and you can sort them with native Mongo by the bid price.
Every time there's a bid, you insert a new document to this collection.
Another option is embedding an array of Bids objects in the item Object.
Each Bid object should include:
bids: [{
account: ObjectId("") # This is the account
price: Number
}]
The cons here are that querying it and accessing it will require more complex queries.
You can read more about the considerations
here:
https://docs.mongodb.com/manual/core/data-model-design
https://coderwall.com/p/px3c7g/mongodb-schema-design-embedded-vs-references
The way you decided to implement your functionality is a little bit complicated.
It is not impossible to do that but, the better way is to use array of objects instead of a single object like this:
{
name: '',
..
..
bids: [{
user: 100032,
price: 30000
}, {
user: 100084,
price: 34000
}]
}

After using the $nearSphere operator, how can I re-sort the results of this mongodb query?

I'm querying a mongoose model called Service using the mongodb $nearSphere operator, to return results by distance from a given point:
Service.find({
$nearSphere: {
$geometry: {
type : "Point",
coordinates : [myLng, myLat]
}
}
})
.limit(10)
$nearSphere sorts by distance, but some of my services are "promoted", so I'd like to float them to the top of the list regardless of distance.
I would normally use the $sort operator for this, but I noticed a note in the mongodb documentation discouraging it.
Is it possible to bring some results to the top of the list, but otherwise preserve the existing distance sorting?
Ideally I'd prefer to do without:
making a second query
refactoring everything to use aggregations instead, because I'm worried about performance (Is this a legitimate concern? Should I be?)
If you don't want to sort based on distance, but on some other field, you should use $geoWithin instead of $nearSphere. This is the only reason why MongoDB documentation discourages to use $sort with $nearSphere. Since you'll be doing sorting two times, which is resource and time wastage when using both $nearSphere and $sort.
If you want to do sorting based on a combination of another field (say 'A') and distance both (when field 'A' is equal in multiple docs), then you need to use aggregation pipeline with $geoNear operator, it will add distance field to your results and you can do the sorting as the way you want.
Super exited to share this after great insights from the reply from Goel.
Just as he mention, to sort by multiple fields, in your case, you would need to aggregate that,with a geonear query which adds a distance field to your documentst that you would give a custom name and combine it with the other field you wish to combine with.Here is an example:
consider sample document(s):
[{
_id: xxxx,
location: {type: "Point", coordinates: [35.00, 1.00]},
createdAt: ISODate("2020-05-18T09:31:24Z"),
...other fields
}]
we can query and retrieve documents based on a particular point, sorted by both closeness and creation time as
{$geoNear:
{near: {type: "Point", coordinates: [35.268428,2.},
spherical: true, distanceField: 'distance' }},
{$sort: {distance: 1, createdAt: -1} },
{$limit: 5},
{$project:{distance1, createdAt: 1 } } ])
.pretty()

Mongoose aggregation, how perform 'select as'?

I have just started working with Mongo Db using Mongoose.js library.
I have two schemas, Orders and Payments.
I want to make a simple query to get all orders with property of sum of all payments for that order.
Schemas
Order
{ name : String }
Payment
{ date : Date, order : Schema.ObjectId, sum : Number }
In mysql I would do it this way:
select *,(select SUM(sum) from payments where payments.order = orders.id group by payments.order) as sum from orders
I was looking for aggregation methods to build the query. But all examples I have found is for the single object. I have no trouble to get the sum for single order (using findOne() and aggregation). But how I do it for array of orders?
There is no way to do this with just one query in MongoDB with the schema design you have. Your schema is fine for a relational DB, but it might not be ideal for NoSQL.
With your schema you would have to do two queries:
An aggregation job on payment to sum the payments grouped by order
A a find(),or findOne(), on order to get the name of the order
An alternative schema would be to have just one collection of order documents and storing the payments as sub-documents on the order document, eg. an order would look something like this:
{
name: "Foo"
payments: [ { date: xxxx, sum: 42.00}, { date: yyyy, sum: 12.00} ]
}
Take a look at the Mongo documentation on data models for more: http://docs.mongodb.org/manual/data-modeling/#data-models

How to calculate Rating in my MongoDB design

I'm creating a system that users can write review about an item and rate it from 0-5. I'm using MongoDB for this. And my problem is to find the best solution to calculate the total rating in product schema. I don't think querying all comments to get the size and dividing it by total rating is a good solution. Here is my Schema. I appreciate any advice:
Comments:
var commentSchema = new Schema({
Rating : { type: Number, default:0 },
Helpful : { type: Number, default:0 },
User :{
type: Schema.ObjectId,
ref: 'users'
},
Content: String,
});
Here is my Item schema:
var productSchema = new Schema({
//id is barcode
_id : String,
Rating : { type: Number, default:0 },
Comments :[
{
type: Schema.ObjectId,
ref: 'comments'
}
],
});
EDIT: HERE is the solution I got from another topic : calculating average in Mongoose
You can get the total using the aggregation framework. First you use the $unwind operator to turn the comments into a document stream:
{ $unwind: "$Comments" }
The result is that for each product-document is turned into one product-document per entry in its Comments array. That comment-entry is turned into a single object under the field Comments, all other fields are taken from the originating product-document.
Then you use $group to rejoin the documents for each product by their _id, while you use the $avg operator to calculate the average of the rating-field:
{ $group: {
_id: "$_id",
average: { $avg: "$Comments.Rating" }
} }
Putting those two steps into an aggregation pipeline calculates the average rating for every product in your collection. You might want to narrow it down to one or a small subset of products, depending on what the user requested right now. To do this, prepend the pipeline with a $match step. The $match object works just like the one you pass to find().
The underlying question that it would be useful to understand is why you don't think that finding all of the ratings, summing them up, and dividing by the total number is a useful approach. Understanding the underlying reason would help drive a better solution.
Based on the comments below, it sounds like your main concern is performance and the need to run map-reduce (or another aggregation framework) each time a user wants to see total ratings.
This person addressed a similar issue here: http://markembling.info/2010/11/using-map-reduce-in-a-mongodb-app
The solution they identified was to separate out the execution of the map-reduce function from the need in the view to see the total value. In this case, the optimal solution would be to run the map-reduce periodically and store the results in another collection, and have the average rating based on the collection that stores the averages, rather than doing the calculation in real-time each time.
As I mentioned in the previous version of this answer, you can improve performance further by limiting the map-reduce to addresing ratings that were created or updated more recently, or since the last map-reduce aggregation.

Resources