Get Group list with Member data where User is not member or owner of group - mongodb

I have Two Collection like following, I'm confused with the query can any one please help to solve,
Group:
var WorkKrewSchema = mongoose.Schema({
content_detail: {
name: String,
description: String
},
business_info: {
name: String,
address: String,
city: String,
state: String,
zipcode: String,
website: String,
starting_hours: Date,
ending_hours: Date,
days: Array
},
created_by: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
created_on: Date,
updated_on: Date
});
Members:
var KrewPartnerSchema = mongoose.Schema({
Work_krew_id: {
type: mongoose.Schema.Types.ObjectId
},
members: [
{
refrence_id: mongoose.Schema.Types.ObjectId,
is_sub_admin: {
type: Boolean,
default: false
},
status: {
type: String,
enum: ['accepted', 'declined', 'pending']
},
date_of_join: Date
}
]
});
get Groups where:
User is not owner of that group as well as not member,
If user is member then status should be Pending

Assuming you have a collection Groups that contains documents of the following form:
{
"_id" : ObjectId("598311f3c63f57ec3dcac86f"),
"grp_name" : "groupA",
"created_by" : "userA"
}
...and a collection Members that contains documents of the following form:
{
"_id" : ObjectId("59831310c63f57ec3dcac891"),
"grp_id" : ObjectId("598311f3c63f57ec3dcac86f"),
"members" : [
{
"user_id" : "UserY",
"status" : "pending"
},
{
"user_id" : "UserA",
"status" : "pending"
},
{
"user_id" : "UserB",
"status" : "approved"
}
]
}
...you can run the following query for a user called "UserA" which should do what you need:
db.getCollection('Members').aggregate(
{
$lookup:
{
from: "Groups",
localField: "grp_id",
foreignField: "_id",
as: "group"
}
},
{
$match:
{
$or:
[
{
"members.user_id": { $ne: "UserA" },
"group.0.created_by": { $ne: "UserA" }
},
{
"members":
{
$elemMatch:
{
"user_id": "UserA",
"status": "pending"
}
}
}
]
}
}
)
I would suggest, though, that you merge those two collections into one. A group document should simply contain all its members in which case you wouldn't need the $lookup stuff and could get away with simple queries (without the aggregation framework).

Related

Efficient way to modify an object from array of objects via index and insert new objects in the same array in mongodb?

I have an array available in the db with the key heros.
Heros: [
{
name: 'Ironman',
country: 'USA'
},
{
name: 'Shaktiman',
country: 'India'
},
{
name: 'Black Panther',
country: 'Wakanda'
}
]
and I have a new array which basically have additional heros:
additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
I want to update the name and country of a hero at a specific index say 1 and replace it with new values. After that I have to put all the additional heros in the same query.
After executing update query, expected result would be:
At index 2 -> update name: 'Krishh', country: 'India' -> put rest of the heros
Updated db value for heros is:
Heros: [
{
name: 'Ironman',
country: 'USA'
},
{
name: 'Shaktiman',
country: 'India'
},
{
name: 'Krishh',
country: 'India'
},
{ name: 'Wonder woman',
country: 'USA'
},
{ name: 'Kenshiro',
country: 'Japan'
}
]
I'm aware of the part to push a value but I can't find any efficient way to update and insert at once
$push: { heros: { $each: additionalHeros } }
The regular update query will not allow to do 2 operations in the same field, it will create a conflict error
You can use update with aggregation pipeline starting from MongoDB 4.2, but i think this approach is not efficient and you want an efficient approach,
$map to iterate loop of heros array
$indexOfArray to get the index of the current object in heros array
$cond to check if the above return index match with our input index then update the latest value otherwise return the same object
second $set stage to add new heros using $concatArrays operator
additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
updateIndex = {
name: "Krishh",
country: "India"
}
index = 1
db.collection.updateOne(
{}, // your query
[{
$set: {
heros: {
$map: {
input: "$heros",
in: {
$cond: [
{
$eq: [
{
$indexOfArray: [
"$heros",
{
name: "$$this.name",
country: "$$this.country"
}
]
},
index
]
},
updateIndex,
"$$this"
]
}
}
}
}
},
{
$set: {
heros: {
$concatArrays: ["$heros", additionalHeros]
}
}
}]
)
Playground
You can do two separate update queries or bulkWrite query
update specific index property
db.collection.updateOne(
{}, // your query
{
$set: {
"heros.1.name": "Krishh",
"heros.1.country": "India"
}
}
)
Playground
push array in heros
db.collection.updateOne(
{}, // your query
{
$push: {
heros: {
$each: [
{
name: "Wonder woman",
country: "USA"
},
{
name: "Kenshiro",
country: "Japan"
}
]
}
}
}
)
Playground
this will work
let document be
{
"_id" : "heroes",
"Heros" : [
{
"name" : "Ironman",
"country" : "USA"
},
{
"name" : "Shaktiman",
"country" : "India"
},
{
"name" : "Black Panther",
"country" : "Wakanda"
}
]
}
Code would be
let additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
let index = 2;
let data = { name: 'Krishh', country: 'India' }
let updateQuery = {};
updateQuery[`Heros.${index}`] = data;
let ops = [];
ops.push(
{
updateOne: {
filter: { _id: "heroes" },
update: {
$set: updateQuery,
},
upsert: true
}
},
);
ops.push(
{
updateOne: {
filter: { _id: "heroes" },
update: {
$push: { Heros: { $each: additionalHeros } }
},
upsert: true
}
},
);
let x = await db1.bulkWrite(ops, { ordered: true, upsert: true })
Then Output would be
{
"_id" : "heroes",
"Heros" : [
{
"name" : "Ironman",
"country" : "USA"
},
{
"name" : "Shaktiman",
"country" : "India"
},
{
"name" : "Krishh",
"country" : "India"
},
{
"name" : "Wonder woman",
"country" : "USA"
},
{
"name" : "Kenshiro",
"country" : "Japan"
}
]
}

Join two collections with id stored in array of objects in mongodb

I have two collections with name School & Students
School Collection
{
_id: ObjectId("60008e81d186a82fdc4ff2b7"),
name: String,
branch: String,
class: [{
"active" : true,
"_id" : ObjectId("6001e6871d985e477b61b43f"),
"name" : "I",
"order" : 1
},
{
"active" : true,
"_id" : ObjectId("6001e68f1d985e477b61b444"),
"name" : "II",
"order" : 2
}]
}
Student Collection
{
_id: ObjectId("6002def815eccd53a596f830"),
schoolId: ObjectId("60008e81d186a82fdc4ff2b7"),
sessionId: ObjectId("60008e81d186a82fdc4ff2b9"),
class: ObjectId("6001e6871d985e477b61b43f"),
}
I want to get the data of Student Collection in single query.
I have class id stored in Student Collection and data against that id is stored in School Collection under class key, which is array of objects.
Can you please help me in getting the class object in student collection with this id?
Output i want:
data: {
_id: ObjectId("6002def815eccd53a596f830"),
schoolId: ObjectId("60008e81d186a82fdc4ff2b7"),
sessionId: ObjectId("60008e81d186a82fdc4ff2b9"),
class: ObjectId("6001e6871d985e477b61b43f"),
classData: [{
"active" : true,
"_id" : ObjectId("6001e6871d985e477b61b43f"),
"name" : "I",
"order" : 1
}]
}
So I tried this but it didn't work:
const students = await this.studentModel.aggregate([
{
$lookup: {
from: 'School',
let: { classId: '$class' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$classId', '$class._id'] },
},
},
],
as: 'classData',
},
},
]);
$lookup, pass schoolId and class in let,
$match school id condition
$filter to iterate loop of class and filter specific class object
$arrayElemAt will get object from returned result from $filter
$replaceRoot to replace object to root
const students = await this.studentModel.aggregate([
{
$lookup: {
from: "School",
let: {
schoolId: "$schoolId",
classId: "$class"
},
pipeline: [
{ $match: { $expr: { $eq: ["$$schoolId", "$_id"] } } },
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$class",
cond: { $eq: ["$$this._id", "$$classId"] }
}
},
0
]
}
}
}
],
as: "classData"
}
}
])
Playground

How to calculate average rating of my product in mongodb?

This is the query I used to get the average of rating, but the result was null:
db.products.aggregate([{$unwind: "$reviews"}, {$group: {_id: "$reviews", ratingAvg: {$avg: "$rating"}}}])
This is my product schema:
category: String,
name: String,
price: Number,
image: String,
description: String,
stock: Number,
reviews: [
{
type: mongoose.Schema.Types.ObjectID, ref: 'Review'
}
})
This is my review schema:
text: String,
rating: Number,
// author: {
// id: {type: mongoose.Schema.Types.ObjectID, ref: 'User'},
// name: String
// }
author: String
})
Every time I run this query I get:
{ "_id" : ObjectId("5f79d1b4b4b3c1061f1fbe52"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d1a5b4b3c1061f1fbe51"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d196b4b3c1061f1fbe4f"), "ratingAvg" : null }
It is showing ratingAvg as"null" whereas I have ratings for that product in my database.
$lookup helps to join two collections.
[
{
"$lookup": {
"from": "Review",
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}
},
{
$unwind: "$reviews"
},
{
$group: {
_id: null,
ratingAvg: {
$avg: "$reviews.rating"
}
}
}
]
Then you can easily find the average using $avg along with $group after destructure the array using $uniwnd
Working Mongo playground

Return custom results using the Aggregate framework

I've the following Schemas in my Node.js app:
let CategorySchema = mongoose.Schema({
name: { type: String, required: true }
});
let UserSchema = mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true }
});
let CustomerSchema = mongoose.Schema({
name: { type: String, required: true }
});
let VendorSchema = mongoose.Schema({
userID: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
category: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }],
products: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Product' } ],
name: { type: String, required: true }
});
let ProductSchema = mongoose.Schema({
vendorID: { type: mongoose.Schema.Types.ObjectId, ref: 'Vendor' },
name: { type: String, index: true, required: true },
customerReviews: [{
stars: { type: Number, required: false },
review: { type: String, required: false },
customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true }
}]
});
and I am trying to query the Vendors collection to return the following result:
{
_id: "dsi9dsik129dkdsdsds",
userData: {
_id: "dsa9dskd2kwd29dkkss",
firstName: "Michael",
lastName: "White"
},
categoryData: {
_id: "e9dids91i239dskds91",
name: "Category A"
},
productsData: [ {
_id: "3132139i31j32131",
vendorID: "dsi9dsik129dkdsdsds",
name: "Product ABC",
customerReviews [{
stars: "5",
review: "Good",
customerData: {
_id: "zds91i232131j2321j",
name: "John silver"
}
},
{
stars: "3",
review: "Bad",
customerData: {
_id: "ldso91232131j2321j",
name: "Mark Spenser"
}
}]
} ],
name: "Vendor XYZ"
}
I've built the following query on Vendors collection (meaning Vendors.aggregate(...) ) yet I am not sure how to format the returned results as well as retrieve Customers data in customers review, so I was wondering if anyone can help? Thanks.
(
{ $lookup: { from: "users", localField: "userID", foreignField: "_id", as: "userData" } },
{ $lookup: { from: "categories", localField: "category", foreignField: "_id", as: "categoryData" } },
{ $lookup: { from: "products", localField: "products", foreignField: "_id", as: "productsData" } },
{ $group: { _id: null, content: { $push: '$$ROOT' },count: { $sum: 1 } } },
{ $project: { content: { $slice: [ '$content', 0, 10 ] }, count: 1, _id: 0 } },
)
Test:
Category:
{
"_id" : ObjectId("5de7fc530ce9d05be4024170"),
"categoryName" : "Glass",
"__v" : 0
}
User:
{
"_id" : ObjectId("5d6671ae7be3be4e18ebe9bb"),
"firstName" : "Mark",
"lastName" : "Smith",
"__v" : 0
}
Customer:
{
"_id" : ObjectId("5dd7cb11f4b2544253368f24"),
"customerName" : "Michael White",
"__v" : 0
}
Vendor:
{
"_id" : ObjectId("5de7fc6a0ce9d05be4024171"),
"category" : [
ObjectId("5de7fc530ce9d05be4024170")
],
"products" : [
ObjectId("5de8474ccd0bbc05256db819")
],
"userID" : ObjectId("5d6671ae7be3be4e18ebe9bb"),
"vendorName" : "Michael White",
"__v" : 0
}
Product:
{
"_id" : ObjectId("5de8474ccd0bbc05256db819"),
"vendorID" : ObjectId("5de7fc6a0ce9d05be4024171"),
"name" : "Red Sause",
"customerReviews" : [
{
"moderated" : false,
"_id" : ObjectId("5de7fcf20ce9d05be4024175"),
"customerId" : ObjectId("5dd7cb11f4b2544253368f24"),
"stars" : 3,
"review" : "Didn't like it that much :( ",
"date" : ISODate("2019-12-04T18:37:38.253Z")
}
],
"__v" : 0
}
Note: I've added entity name before name in the test above, ex. vendorName / customerName just to avoid confusion between the field 'name' across multiple collections
You can try below aggregation from 3.6 version.
{"$lookup":{
"from": "users",
"localField": "userID",
"foreignField": "_id",
"as": "userData"
}},
{"$unwind":"$userData"},
{"$lookup":{
"from": "categories",
"localField": "category",
"foreignField": "_id",
"as": "categoryData"
}},
{"$lookup":{
"from":"products",
"let":{"products":"$products"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$products"]}}},
{"$unwind":{"path":"$customerReviews", "preserveNullAndEmptyArrays":true}},
{"$lookup":{
"from":"customers",
"localField":"customerReviews.customerId",
"foreignField":"_id",
"as":"customerData"
}},
{"$unwind":{"path":"$customerData", "preserveNullAndEmptyArrays":true}},
{"$group":{
"_id":"$_id",
"vendorID": {"$first":"$vendorID"},
"name": {"$first":"$name"},
"customerReviews":{
"$push":{
"stars": "$customerReviews.stars",
"review": "$customerReviews.review",
"customerData":"$customerData"
}
}
}}
],
"as":"productsData"
}}

Query embedded document in mongoDB

I'm trying to query a Group document that has Users sub documents.
But I only want the subdocuments that belong to the user,
but I'm also getting data of other users.
Look at the query result, UserStatus field, only one of them belongs to the user.
The query result -
[
{
"_id": "5b1bcc12d5cdbf2cf78f2133",
"name": "First group ever",
"users": [
{
userId: 1,
"userStatus": "I"
},
{
userId: 2,
"userStatus": "I"
}
]
}
]
Group.js -
const GroupSchema = mongoose.Schema({
name: {
type: String,
required: true
},
users: [{
userId: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'users',
required: true
},
userStatus: {
type: String,
required: true
}
}]
})
Query -
Group.find({
"users": {
$elemMatch: {
userId: req.params.id,
$or: [{
userStatus: "I"
}, {
userStatus: "A"
}]
}
}
}, "_id name users.userStatus",
function (err, groups) {
if (err)
res.send(err)
res.json(groups);
});