Check follow status in aggregate mongo / mongoose - mongodb

I have this schema for users where followers/followed is array and the reference the same schema
var userSchema = new Schema({
username: { type: String, unique: true, trim: true, required: true },
password: { type: String, required: true },
followers: [{ type: Schema.Types.ObjectId, ref: "users" }],
followed: [{ type: Schema.Types.ObjectId, ref: "users" }],
registered: { type: Date, default: Date.now },
admin: { type: Number, default: 0 }
});
What I am looking for to return the follow status, if the _id is contains in followed array give me for example follow_status: 1
[
{
$match: { username: new RegExp(username, "i") }
},
{
$unwind: "$followers"
},
{
$lookup: {
from: "users",
localField: "followers",
foreignField: "_id",
as: "info"
}
},
{
$unwind: "$info"
},
{
$project: {
info: {
_id: 1,
username: 1,
avatar: { $ifNull: ["$avatar", ""] },
fullname: { $ifNull: ["$fullname", ""] }
}
}
},
{
$replaceRoot: { newRoot: "$info" }
},
{
$limit: 1000
}
]
Current pipeliens result
[
{
"_id": "5a906653f52e66c9c7a23cb6",
"username": "User1"
},
{
"_id": "5a908eb564a726cf8ec7e0a3",
"username": "User2"
}
]

Related

MongoDb How to do complex aggregation to an order Model

Hello Guys This is my Order Model :
const OrderSchema = new Schema({
orderItems: {
name: { type: String, required: true },
qty: { type: Number, required: true },
image: { type: String, required: false },
price: { type: Number, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true,
},
},
totalPrice: { type: Number, required: true },
paymentMethod: { type: String, required: true },
paymentResult: {
status: { type: String, default: 'Waiting For Payment' }
},
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
isPaid: { type: Boolean, default: false },
paidAt: { type: Date },
OrderResult: {
status: { type: String, default: 'Waiting For Payment' }
},
isDelivered: { type: Boolean, default: false },
},{timestamps : true})
I did do This aggregation to get Users Orders Summary :
const usersOrderSummary = await Order.aggregate(
[
{$match:{} },
{$group: {_id:"$user", TotalSpent: {$sum:"$totalPrice"},TotalOrders: { $sum: 1 }}},
]
)
The Result Of this aggregation is an array of:
{
"usersOrderSummary": [
{
"_id": "6216687c0e0d9122f710a1a6",
"TotalSpent": 0.9,
"TotalOrders": 8
},
{
"_id": "628e4b96a7fd3bad9482a81c",
"TotalSpent": 9.18,
"TotalOrders": 53
}
]
}
I want your Help to do an aggregation to get this result:
I want to to do aggregation to collect the sum of completed orders and the sum of the amount that spent in this completed order
the condtion of completed order so i can only considered an order completed when
isDelivered:true
please check the schema above so it would be clear to know how order considered as completed
{
"usersOrderSummary": [
{
"_id": "6216687c0e0d9122f710a1a6",
"TotalSpent": 0.9,
"TotalOrders": 8,
"TotalCompletedOrderSpent": 0.1,
"TotalCompletedOrders": 1,
},
{
"_id": "628e4b96a7fd3bad9482a81c",
"TotalSpent": 9.18,
"TotalOrders": 53
"TotalCompletedOrderSpent": 4,
"TotalCompletedOrderSpent": 2,
}
]
}
Use $cond
db.collection.aggregate([
{
$match: {}
},
{
$group: {
_id: "$user",
TotalSpent: { $sum: "$totalPrice" },
TotalOrders: { $sum: 1 },
TotalCompletedOrderSpent: {
$sum: {
$cond: {
if: { $eq: [ "$isDelivered", true ] },
then: "$totalPrice",
else: 0
}
}
},
TotalCompletedOrders: {
$sum: {
$cond: {
if: { $eq: [ "$isDelivered", true ] },
then: 1,
else: 0
}
}
}
}
}
])
mongoplayground

mongoose : Users who have not logged in since last 5 days

I have 2 schemas
userSchema:
const userSchema = new mongoose.Schema({
firstName: {
type: String,
},
lastName: {
type: String,
},
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
tokens: {
type: [
{
token: String,
},
],
},
});
useractivity:
const userActivitySchema = new mongoose.Schema({
ip: String,
ua: String,
date: {
type: Date,
default: Date.now,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
});
I am adding a new entry in useractivity every time user logged into the system.
I wanted to featch list of users who haven't logIn in last 5 days for that I write query :
const dateToCheck = new Date();
dateToCheck.setDate(
dateToCheck.getDate() - process.env.LEASS_ACTIVE_USER_DAYS
);
const userActivity = await UserActivity.aggregate([
{ $match: { date: { $lt: dateToCheck } } },
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userdata",
},
},
{ $unwind: { path: "$userdata" } },
{
$project: {
date: 1,
"userdata.firstName": 1,
},
},
]);
with this, I am able to get date and firstName of users who haven't logged in last 5 days.
But the issue with this is as there are multiple records in useractivity, above query returns duplicate users in the result.
I tried using { $group: { _id: { user: "$user" } } } to group the result by foreign key but it returns [] in this case.
Any suggestions will be helpfull
Try this out, you already have access to the unique id of the user from your lookup, so use that to group your documents. Additionally, I am storing all the dates in an array from the grouping
UPDATE: Also attaching mongo playground link where I tried it
const userActivity = await UserActivity.aggregate([
{ $match: { date: { $lt: dateToCheck } } },
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userdata",
},
},
{ $unwind: { path: "$userdata" } },
{
$project: {
date: 1,
"userdata.firstName": 1,
"user_id": "$userdata._id"
},
},
{
$group: {
"_id": {
"user_id": "$user_id",
"user_name": "$userdata.firstName"
},
"dates": {
"$push": "$date"
}
}
}
]);

mongoose recursive nesting

in my project a user can create products. each user have a reference to all of its products and each product have a reference to its user.
both the user and the product have a 'name' field.
i need to get all of the users products array, and in that array i want to have the product name and the
user name that created it (and only those fields and no others).
for example:
Users:
{ _id: 1, name: 'josh', productIds: [1,3]}
{ _id: 2, name: 'sheldon', productIds: [2]}
Products:
{ _id: 1, name: 'table', price: 45, userId: 1}
{ _id: 2, name: 'television', price: 25 userId: 2}
{ _id: 3, name: 'chair', price: 14 userId: 1}
i want to get the following result:
{ _id: 1, name: 'josh',
products: {
{ _id: 1, name: 'table', user: { _id: 1, name: 'josh' },
{ _id: 3, name: 'chair', user: { _id: 1, name: 'josh' },
}
}
{ _id: 2, name: 'sheldon',
products: {
{ _id: 2, name: 'television', userId: { _id: 2, name: 'sheldon' }
}
}
i tried the following query that didn't fill the inner userId and left it with only the id (no name):
User.aggregate([
{
$lookup:
{
from: 'products',
localField: 'productIds',
foreignField: '_id',
as: 'products'
}
}
i also tried the following, which did the same as the first query except it only retried the first product for each user:
User.aggregate([
{
$lookup:
{
from: 'products',
localField: 'productIds',
foreignField: '_id',
as: 'products'
}
},
{
$unwind: {
path: "$products",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "user",
localField: "products.userId",
foreignField: "_id",
as: "prodUsr",
}
},
{
$group: {
_id : "$_id",
products: { $push: "$products" },
"doc": { "$first": "$$ROOT" }
}
},
{
"$replaceRoot": {
"newRoot": "$doc"
}
}
Product:
const schema = new Schema(
{
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
}
);
module.exports = mongoose.model('Product', schema);
User:
const schema = new Schema(
{
name: {
type: String,
required: true,
unique: true
},
productIds: [{
type: Schema.Types.ObjectId,
ref: 'Product',
require: false
}],
{ timestamps: true }
);
module.exports = mongoose.model('User', schema);
any help will be highly appreciated
It looks like a perfect scenario for $lookup with custom pipeline and another nested $lookup. The inner one allows you to handle product-> user relationship while the outer one handles user -> product one:
db.Users.aggregate([
{
$project: {
productIds: 0
}
},
{
$lookup: {
from: "Products",
let: { user_id: "$_id" },
pipeline: [
{
$match: {
$expr: {
$eq: [ "$userId", "$$user_id" ]
}
}
},
{
$lookup: {
from: "Users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$project: {
"user.productIds": 0,
"price": 0,
"userId": 0
}
}
],
as: "products"
}
}
])
Mongo Playground

How to query to get all documents referenced to another collection based on one field as username

In my Node API and MongoDB, I'm trying to make an endpoint to get all the posts associated with one username of a profile. In my Profile schema, I have a reference to the Post Schema and in my Post schema, I have a reference to the Username from Profile schema.
My issue I don't know how to get all the posts for that username. I did similarly but with embedded for the Experience schema but I'm not sure how to do that for the referenced collections.
Post model:
const { Connect } = require("../db");
const reactionSchema = {
likedBy: {
type: String,
unique: true,
sparse: true
}
};
const postSchema = {
text: {
type: String,
required: true,
unique: true,
sparse: false
},
username: {
type: Connect.Schema.Types.String,
ref: "Profile"
},
image: {
type: String,
default: "https://via.placeholder.com/150",
required: false
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
reactions: [reactionSchema],
comments: {
type: Connect.Schema.Types.ObjectId,
ref: "Comment",
required: false
}
};
const collectionName = "post";
const postSchemaModel = Connect.Schema(postSchema);
const Post = Connect.model(collectionName, postSchemaModel);
module.exports = Post;
Profile model:
// Here defining profile model
// Embedded we have the Experience as []
const { Connect } = require("../db");
const { isEmail } = require("validator");
const postSchema = {
type: Connect.Schema.Types.ObjectId,
ref: "Post"
};
const experienceSchema = {
role: {
type: String,
required: true
},
company: {
type: String,
required: true
},
startDate: {
type: Date,
required: true
},
endDate: {
type: Date,
required: false
},
description: {
type: String,
required: false
},
area: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
username: {
type: String,
required: false
},
image: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
}
};
const profileSchema = {
firstname: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: [true, "Email is required"],
validate: {
validator: string => isEmail(string),
message: "Provided email is invalid"
}
},
bio: {
type: String,
required: true
},
title: {
type: String,
required: true
},
area: {
type: String,
required: true
},
imageUrl: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
},
username: {
type: String,
required: true,
unique: true
},
experience: [experienceSchema],
posts: [postSchema],
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
}
};
const collectionName = "profile";
const profileSchemaModel = Connect.Schema(profileSchema);
const Profile = Connect.model(collectionName, profileSchemaModel);
module.exports = Profile;
I was able to make this for the embedded experience but not sure that correct for referenced collection:
const profileWithExperiences = await Student.aggregate([
{ $match: { username: res.username.username } },
{
$unwind: "$experience"
},
{
$match: {
"experience._id": new ObjectID(req.params.experienceId)
}
},
{
$project: {
username: 1,
experience: 1,
_id: 0
}
}
]);
I would like to see an example for referenced collections as it is confusing me how should I do that
[EDIT]
JSON for Profiles and Posts
{
"_id": ObjectId("5e2c98fc3d785252ce5b5693"),
"imageUrl": "https://i.pravatar.cc/300",
"firstname": "Jakos",
"surname": "Lemi",
"email": "lemi#email.com",
"bio": "My bio bio",
"title": "Senior IT developer",
"area": "Copenhagen",
"username": "Jakos",
"experience": [
{
"image": "https://via.placeholder.com/150",
"_id": ObjectId("5e3975f95fbeec9095ff3d2f"),
"role": "Developer",
"company": "Google",
"startDate": ISODate("2018-11-09T23:00:00.000Z"),
"endDate": ISODate("2019-01-05T23:00:00.000Z"),
"area": "Copenhagen",
"createdAt": ISODate("2020-02-04T13:47:37.167Z"),
"updatedAt": ISODate("2020-02-04T13:47:37.167Z")
},
{
"image": "https://via.placeholder.com/150",
"_id": ObjectId("5e3978bf5e399698e20c56d4"),
"role": "Developer",
"company": "IBM",
"startDate": ISODate("2018-11-09T23:00:00.000Z"),
"endDate": ISODate("2019-01-05T23:00:00.000Z"),
"area": "Copenhagen",
"createdAt": ISODate("2020-02-04T13:59:27.412Z"),
"updatedAt": ISODate("2020-02-04T13:59:27.412Z")
}
],
"createdAt": ISODate("2020-01-25T19:37:32.727Z"),
"updatedAt": ISODate("2020-02-04T23:14:37.122Z"),
"__v": NumberInt("0")
}
Post
{
"_id": ObjectId("5e3beb639e072afedd19dcef"),
"username": ObjectId("5e2c98fc3d785252ce5b5693"),
"image": "https://via.placeholder.com/150",
"text": "My awesome post",
"createdAt": ISODate("2020-02-06T10:33:07.22Z"),
"updatedAt": ISODate("2020-02-06T10:33:07.22Z"),
"reactions": [ ],
"__v": NumberInt("0")
}
Output expected:
{
"username": "Jakos",
"postsCount": [1],
"posts": [
{
"_id": ObjectId("5e3beb639e072afedd19dcef"),
"image": "https://via.placeholder.com/150",
"text": "My awesome post",
"createdAt": ISODate("2020-02-06T10:33:07.22Z"),
"updatedAt": ISODate("2020-02-06T10:33:07.22Z"),
"reactions": [ ],
"__v": NumberInt("0")
}
]
}
I want to see all the posts related to that username
You can use the $lookup aggregation to join collections.
db.profiles.aggregate([
{
$match: {
username: "Jakos"
}
},
{
$lookup: {
from: "posts", //the name of the posts collection, change this if it is different
localField: "_id",
foreignField: "username",
as: "posts"
}
},
{
$project: {
username: 1,
posts: 1,
postsCount: {
$size: "$posts"
},
_id: 0
}
}
])
Playground
For mongoose it should be like this in your app:
const profileWithExperiences = await Student.aggregate([
{ $match: { username: res.username.username } },
{
$unwind: "$experience"
},
{
$lookup: {
from: "posts", //the name of the posts collection, change this if it is different
localField: "_id",
foreignField: "username",
as: "posts"
}
},
{
$project: {
username: 1,
posts: 1,
postsCount: {
$size: "$posts"
},
_id: 0
}
}
]);
Try to get like this i am getting data from two table with the use of node and mongodb.
var param = {};
param['user_id']=id;
var search={ user_id:param.user_id,status: [ 'Pending', 'Accepted' ] };
var output={};
output = await JobRequest.find(search);
if(output !=0)
{
var JSon=await JobRequest.aggregate([
{$match: { user_id: {$gte:id}} },
{
"$project": {
"employee_id": {
"$toObjectId": "$employee_id"
},
"service_id": {
"$toObjectId": "$service_id"
},
"createdDate": {
"$toString": "$createdDate"
},
"chat_status": {
"$toString": "$chat_status"
},
"status": {
"$toString": "$status"
},
"delivery_address": {
"$toString": "$delivery_address"
},
"delivery_lat": {
"$toString": "$delivery_lat"
},
"delivery_lang": {
"$toString": "$delivery_lang"
},
}
},
{
$lookup:
{
from: "employees",
localField: "employee_id",
foreignField: "_id",
as: "employee_details"
}
},
{
$lookup:
{
from: "services",
localField: "service_id",
foreignField: "_id",
as: "service_details"
}
}
]);

multiple $lookup stages in mongodb aggregation

I'm working with the followings mongoose schemas:
Question schema:
var Question = new Schema({
title: String,
content: String,
createdBy: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
answers: {
type: [ { type: Schema.Types.ObjectId, ref: 'Answer' } ]
}
});
Answer Shchema:
var Answer = new Schema({
content: {
type: String,
require: 'Content cannot be empty.'
},
createdBy: {
type: Schema.Types.ObjectId,
ref: 'User'
},
isBest: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: Date.now
},
votes: {
type: Number,
default: 0
},
comments: {
type: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
}
});
and Comment Schema:
var Comment = new Schema({
content: {
type: String,
required: [true, 'Content cannot be empty']
},
createdBy: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
}
});
Basically what I'm trying to do is doing a $lookup for answers and for comments array in every answer, and then in $project stage try to add an isOwner field that is going to be true if the user logged is the owner of the answer or comment. This is what I' trying:
Question.aggregate([
{
$match: { '_id': { $eq: questionId } }
},
{
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
},{
$lookup:{
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
}, {
$project: {
title: true,
content: true,
createdBy: true,
createdAt: true,
isOwner: { $eq : ['$createdBy', currentUser] },
answers: true,
answers: {
isOwner: { $eq : ['$createdBy', currentUser] },
content: true,
createdBy: true,
createdAt: true,
comments: {
content: true,
createdAt: true,
createdBy: true,
isOwner: { $eq : ['$createdBy', currentUser] }
}
}
}
}
])
This is the ouput that I'm expecting:
{
"_id": "58a7be2c98a28f18acaa4be4",
"title": "Some title",
"createdAt:": "2017-03-03T05:13:41.061Z",
"content": "some content",
"isOwner": true,
"createdBy": {
"_id": "58a3a66c088fe517b42775c9",
"name": "User name",
"image": "imgUrl"
},
"answers": [
{
"_id": "58a3a66c088fe517b42775c9",
"content": "an answer content",
"createdAt": "2017-03-03T05:13:41.061Z",
"isBest": false,
"isOwner": false,
"createdBy":{
"_id": "58a3a66c088fe517b42775c9",
"name": "another user",
"image": "another image"
},
"comments": [
{
"_id": "58aa104a4254221580832a8f",
"content": "some comment content",
"createdBy": {
"_id": "58a3a66c088fe517b42775c9",
"name": "another user",
"image": "another image"
},
}
]
}
]
}
I'm using mongodb 3.4.2
You can try addFields stage to add the isOwner field for all the relations.
Question.aggregate([{
$match: {
'_id': {
$eq: questionId
}
}
}, {
$addFields: {
"isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, { // No unwind needed as answers is scalar of array values.
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
}, {
$addFields: {
"answers.isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, {
$unwind: "$answers" //Need unwind here as comments is array of scalar array values
}, {
$lookup: {
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
}, {
$addFields: {
"comments.isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, {
$addFields: { // Move the comments back to answers document
"answers.comments": "$comments"
}
}, {
$project: { // Remove the comments embedded array.
"comments": 0
}
}, {
$group: {
_id: null,
isOwner: {
$first: "$isOwner"
},
answers: {
$push: "$answers"
}
}
}])
THe problem with your code is that you have not unwind the answeeres array before lookup
Please check below comment
Question.aggregate([
{
$match: { '_id': { $eq: questionId } }
},
{
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
},
{$unwind : "$answers"}, // <-- Check here
{
$lookup:{
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
},
{
$group : {
_id : null,
title: {$first : true},
content: {$first :true},
createdBy: {$first :true},
createdAt: {$first :true},
isOwner: { $eq : ['$createdBy', currentUser] },
answersStatus: {$first :true},
answers : {$push : $answer}
}
}
])