Mongodb get top 5 grouped by object - mongodb

I am using a MERN stack and need to group by certain attribute of schema, so for example: this is my schema,
const likeSchema = new mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
commentId: {
type: Schema.Types.ObjectId,
ref: 'Comment'
},
questionId: {
type: Schema.Types.ObjectId,
ref: 'Question'
}
}, { timestamps: true })
Now, I want to group by questionId and then return the 5 groups with the highest count. So in essence, I want the top 5 posts with the most likes. Is there a way to do this with mongodb?
Any help is appreciated!

You can first group by questionId, then sort the result and select the top 5 records.
db.collection.aggregate([{
"$group": {
_id: "$questionId",
userId: {
$first: "$userId"
},
commentId: {
$first: "$commentId"
},
questionId: {
$first: "$questionId"
},
count: {
$sum: 1
}
},
},
{ $sort: { "count": -1 } },
{ $limit: 5 },
])

Related

Grouping documents in mongoose

I am building a chat app using nodejs and mongoose. I need to retrieve chats for a user(logged in user) such that the retrieved data/chats will contain the latest message and the name of the user the logged in user had a chat with. I am not able to come app with a way to aggregate the data since the sender can also be a recipient in a chat. My message schema is shown below.
const messageSchema = new mongoose.Schema(
{
content: { type: String, required: true },
fromUser: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
toUser: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
users: Array,
messageRead: { type: Boolean, default: false },
},
{
timestamps: true,
}
);
The purpose of this is to have a list of chats render on the front end which when clicked will open an inbox containing messages between the two users.
You can just use the $or operator. This way it will return all elements where the user is the sender or recipient:
.aggregate([
{
$match: {
$or: [
{
fromUser: ObjectId("<UserID>")
},
{
toUser: ObjectId("<UserID>")
}
]
}
},
{ $sort: { createdDate: -1 } },
{ $limit: 1 },
])
Edit after feedback via chat
I grouped the documents by using the users array and basically just output all fields as in the initial document:
.aggregate([
{
$match: {
users: ObjectId("<UserID>")
}
},
{
$group: {
_id: "$users",
out: {
$top: {
output: {
_id: "$_id",
content: "$content",
fromUser: "$fromUser",
toUser: "$toUser",
users: "$users",
messageRead: "$messageRead",
createdAt: "$createdAt"
},
sortBy: {
createdAt: -1
}
}
}
}
},
{
"$replaceRoot": {
"newRoot": "$out"
}
}
])

Get extra field in model summing all records from lookup document

Having this model:
const matchSchema = mongoose.Schema({
location: {type: mongoose.Types.ObjectId, ref: 'Location'},
datetime: Date,
teamAName: String,
teamBName: String,
teamA: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamB: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamAScore: {type: Number, default: 0},
teamBScore: {type: Number, default: 0},
pichichi: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
mvp: {type: mongoose.Types.ObjectId, ref: 'Player', default:null},
});
"teamA" and "teamB" are lists containing the "_id" of every player. When retrieving a player, I want to retrieve the number of matches that he/she have played. How can I do that? Below my query just retrieving fields from model "Player"
class PlayerController {
getAll(req, res) {
Player.find()
.sort('firstname')
.exec(function(err, players) {
res.send(players);
});
}
}
So, instead of just having this list:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
__v: 0
}
]
I would like to have the amount of matches that every player played:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
matches:6,
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
matches:8,
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
matches: 2,
__v: 0
}
]
Here's one way you could do it.
db.players.aggregate([
{
"$lookup": {
"from": "matches",
"let": { "myId": "$_id" },
"pipeline": [
{
"$match": {
"$expr": {
"$in": [ "$$myId", { "$setUnion": [ "$teamA", "$teamB" ] } ]
}
}
},
{ "$count": "numMatches" }
],
"as": "matchCount"
}
},
{
"$set": {
"matches": {
"$ifNull": [ { "$first": "$matchCount.numMatches" }, 0 ]
}
}
},
{ "$unset": "matchCount" }
])
Try it on mongoplayground.net.

create index for mongodb collection

I have a MongoDB collection
{
id: String,
country: String,
createdAt: Date,
...
}
What is the right indices for the collection using this aggregate?
.aggregate([
{ $match: { country: "US" } },
{ $sort: { createdAt: -1 } },
{
$group: {
_id: { id: '$id' },
totalPrice: { $first: '$totalPrice' },
},
},
])
I have these indices on the schema
schema.index({ country: 1 });
schema.index({ createdAt: 1 });
schema.index({ country: 1, id: 1, createdAt: 1 });
But I think something is not healty

mongoose find if array of references of a document contains an IdObject

im using nodejs express and mongodb
this is part of my schema:
var UserSchema = mongoose.Schema({
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}],
sentRequests: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}],
receivedRequests: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}]
}
var User = mongoose.model('User', UserSchema);
i want to query if a user with a specefic id is in any array ** (friends, sentRequests or receivedRequests) **of another user :
i used this approach but I have to do this for 3 arrays and it's slow:
UserSchema.statics.relStatus = function(User1ID, User2ID) {
var User = this;
User.find({
_id: User1ID,
friends: {
_id: User2ID
}
}).then((err, res) => {
if (res) {
return ' they are friends';
}
});
You can try with following aggregation query (if you are using mongo 3.4 or higher):
User.aggregate([
{ $match: { _id: User1ID } },
{
$facet: {
friends: [{ $match: { friends: User2ID } }],
sentRequests: [{ $match: { sentRequests: User2ID } }],
receivedRequests: [{ $match: { receivedRequests: User2ID } }]
}
},
{
$project: {
relationship: {
$switch: {
branches: [
{ case: { $anyElementTrue: [ "$friends" ] }, then: "friends" },
{ case: { $anyElementTrue: [ "$sentRequests" ] }, then: "sentRequests" },
{ case: { $anyElementTrue: [ "$receivedRequests" ] }, then: "receivedRequests" },
],
default: false
}
}
}
}
])
Result will be object in following format {relationship: "<RELATIONSHIP_TYPE>"}
or {relationship: false} if they have no relationship.
First stage of the aggregation pipeline will match single document by _id (which is indexed by default), so you shouldnt have any performance issues.
In order to query for multiple field you can user '$or'.
User.find({
_id: User1ID,
{
$or: [
{friends: $in:[User2ID]},
{sentRequests: $in:[User2ID]},
{receivedRequests: $in:[User2ID]}
]
}
}).then((err, res) => {
if (res) {
return ' they are friends';
}
});
$or will return a user which have User2ID in either 'friends' or 'sentRequests' or 'receivedRequests'.
Read more about '$or' and '$in'
Hope this helps.

Check if value don't exists in sub document of sub document

I the following schema.
var UserSchema = new Schema({
messages: [{
username: String,
read: [{
type: { type: String, enum: ['A','B','C'] },
}]
}],
});
var MySchema = new Schema({
users: [UserSchema]
});
I need to count how many occurrences in MySchema that the last message of each user don't have the read value as 'A'
I need to use aggregate, so that I can join with an existing query.
This should work, if it does not please include some sample data and expected result so that I will update my answer later:
db.collection.aggregate([
{
$unwind: '$users',
},
{
$project: {
lastMessage: {
$arrayElemAt: ['$users.messages', -1],
},
},
},
{
$unwind: '$lastMessage.read',
},
{
$group: {
_id: '$_id',
read: {
$push: '$lastMessage.read',
},
},
},
{
$match: {
read: {
$nin: ['A']
}
}
},
{
$count: 'count'
}
])