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

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'
}
])

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"
}
}
])

Getting `[ [Object] ]` as nested array of object response on MongoDB Aggregation query

I'm trying to do an aggregation on two collections that has a linkage between them, and I need to access information in an array of objects in one of those collections.
Here are the schemas:
User Schema:
{
_id: ObjectId,
username: String,
password: String,
associatedEvents: [
{
event_id: ObjectId,
isCreator: boolean,
access_level: String,
}
]
}
Event Schema:
{
_id: ObjectId,
title: String,
associated_users: [
{
user_id: ObjectId
}
]
}
I'm attempting to get the users associated to an event for a specific user, and then get their access level information. Here's the aggregation I have:
const eventsJoined = await Event.aggregate([
{
$match: {
$expr: { $in: [id, "$associatedUserIds"] },
},
},
{
$lookup: {
from: "users",
localField: "associatedUserIds",
foreignField: "_id",
as: "user_info",
},
},
{ $unwind: "$user_info" },
{
$unwind: {
path: "$user_info.associatedEvents",
preserveNullAndEmptyArrays: true,
},
},
{
$group: {
_id: "$_id",
title: { $first: "$title" },
description: { $first: "$description" },
startDate: { $first: "$startdate" },
userInfo: { $first: "$user_info" },
usersAssociatedEvents: { $push: "$user_info.associatedEvents" },
},
},
{
$project: {
title: 1,
description: 1,
startDate: 1,
userInfo: 1,
usersAssociatedEvents: "$usersAssociatedEvents",
},
},
]);
And this is the result I'm getting:
[
{
_id: 609d5ad1ef4cdbeb32987739,
title: 'hello',
description: 'desc',
startDate: null,
usersAssociatedEvents: [ [Object] ]
}
]
As you can see, the query is already aggregating the correct data. But the last thing that's tripping me up is the fact that the aggregation is [ [Object] ] for usersAssociatedEvents instead of the actual contents of the object. Any idea on why that would be?

Mongodb get top 5 grouped by object

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 },
])

aggregation on replies and comments with same user in mongodb

Imagine we have the structure like the following:
comments: [{
user: '...',
upVotes: 12,
text: '...'
replies: [{
user: '...',
upVotes: 34,
text: '...'
}]
}]
what we want is to retrieve the user and text of comments and replies with the same user!
I have implemented the following query but it doesn't work:
db.getCollection('links').aggregate([
{
$unwind: "$comments"
},
{
$unwind: "$comments.replies"
},
{
$project: {
sameAuthor: {
$eq: ["$comments.user", "$comments.replies.user"]
},
}
}
])
I don't know where the problem is!
If you need more clear result please add result sample in the comment.
db.links.aggregate([
{ $unwind: '$comments'},
{ $unwind: '$comments.replies'},
{ $match: { $expr: { $eq: ["$comments.user", "$comments.replies.user"] } } },
{ $group: {
_id: '$_id',
user: { $first: "$comments.user" },
commentText: { $first: "$comments.text" },
repliesTaxt: { $push: "$comments.replies.text" }
}
}
])

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.