How would I be able to query depending on array length? - mongodb

I have a project where a user selects a group and then can choose what role they are looking for in that group.
It looks something like this:
let group = await models.Group.findById(user.groupID);
let seekGroupRole = await models.GroupRole.findById(user.seekingGroupRole);
let test = await models.Profile.aggregate([
{
$match: {
$or: [
{ _id: { $in: group.members } },
{ _id: { $in: seekGroupRole.profiles } },
],
},
},
{ $group: { _id: "$_id" } },
]);
console.log(test);
Now we are adding a way to select multiple roles. How would I go about doing this without having to do multiple queries?
Is something like this possible?
let group = await models.Group.findById(user.groupID);
let seekGroupRole = await models.GroupRole.find({
_id: { $in: user.seekingGroupRole}
});
let test = await models.Profile.aggregate([
{
$match: {
$or: [
{ _id: { $in: group.members } },
{ _id: { $in: seekGroupRole.profiles } },
],
},
},
{ $group: { _id: "$_id" } },
]);
console.log(test);
Now seekGroupRole is an array. It could have the length of 1 or more. So it would be seekGroupRole[0].profiles, seekGroupRole[1].profiles, etc...
What is the best way to accomplish this? Should I for loop and merge the results?

What I ended up doing is using for loop with concat to avoid multiple queries.
let seekGroupRole = await models.GroupRole.find({
_id: { $in: user.seekingGroupRole}
});
let allRoles;
for (let i = 0; i < seekGroupRole.length; i++) {
if (i == 0) {
allRoles = seekGroupRole[i].profiles;
}
if (i > 0) {
allRoles = allRoles.concat(
seekGroupRole[i].profiles
);
}
}
let test = await models.Profile.aggregate([
{
$match: {
$or: [
{ _id: { $in: group.members } },
{ _id: { $in: allRoles } },
],
},
},
{ $group: { _id: "$_id" } },
]);

Related

I need left anti join in mongodb with lookup and match aggregate function

I want to convert this query in MongoDB
Select * from Question where not exists (select * from Solved_question where questionid = id and username=username)
Collections Schema
question:{
_id:ObjectId,
title:String,
desc:string,
Author:string
},
User:{
_id:ObjectId,
Email:string,
Password:string
},
Solvedquestioncollection:{
Id:_id,
QuestionId:{ type:mongoose.Schema.ObjectId, ref:"Question" },
UserId:{ type:mongoose.Schema.ObjectId, ref:"User" }
}
Sample Document
Question:[
{
_id:ObjectId('1'),
title:"main component of computer",
desc:"some desc for this"
author:"ashick"
},
{
_id:ObjectId('2'),
title:"Advantage of CPU",
desc:"some desc for this"
author:"ashick"
},
]
User:[
{
_id:ObjectId('1'),
email:"as#g.com",
password:"12345"
},
{
_id:ObjectId('2'),
email:"df#g.com",
password:"345"
}
]
solvedquestion:[
{
_id:ObjectId('1'),
question:ObjectId('2'),
userId:ObjectId('1')
},
{
_id:ObjectId('2'),
question:ObjectId('2'),
userId:ObjectId('2')
}
]
I want to Fetch the Question in Question Collection Which does not solved by a particular user
I try this
question = await Question.aggregate([
{
$lookup: {
from: "solvedquestions",
let: { userId: "$userId" },
pipeline: [
{
$match: {
$expr: {
$eq: ["ObjectId('60ebc6b9980b8e1f8cffe34b'"), "$$userId"],
},
},
},
],
as: "resultingArray",
},
},
]);
but its Return the Empty Array
Thank you for your answer
try {
let aggregate = Question.aggregate([
{
$lookup: {
from: 'solvedquestions',
let: {
question_id: '$_id',
question_userId: '$user'
},
pipeline: [ {
$match: {
$expr: {
$and: [
{
$eq: ['$QuestionId', '$$question_id']
},
{
$eq: ['$userId', '$$question_userId']
}
]
}
}
}],
as: 'results'
}
}
]);
return await aggregate.exec();
} catch (error) {
console.error('Error ->',error);
}
I hope this helps, Let me know if you have any doubt. Happy to help :)

mongodb $pull in array with another array

Help me please to remove elements in an array with such document schema:
contracts: [
{
bandwidth: {
calculated: {
value: Number,
documents: [id1, id2, id3, id4],
}
}
}
]
I want to delete elements in all documents array that are in filter array.
I tried:
const documentsToDelete = [id2, id3]
const points = await Point.updateMany({
$pull: {
contracts: {"bandwidth.calculated.documents": {$in: documentsToDelete}}
},
});
But it does not work. The resulting array must contain "id1" and "id4"
Correct the things,
the first parameter is the query part to match the condition
the contracts is an array so use $[] to update operation in all elements
const documentsToDelete = [id2, id3];
const points = await Point.updateMany(
{},
{
$pull: {
"contracts.$[].bandwidth.calculated.documents": {
$in: documentsToDelete
}
}
}
)
Playground
bellow quires worked for me well
let feed = await Feed.findOneAndUpdate(
{
_id: req.params.id,
feeds: {
$elemMatch: {
type: FeedType.Post,
locations: {
$elemMatch: {
uniqueName,
},
},
},
},
},
{
$pull: {
//#ts-ignore
'feeds.$[].locations': { uniqueName },
},
},
{ new: true }
);
or
let feed = await Feed.findOneAndUpdate(
{
$and: [
{
_id: req.params.id,
},
{
'feeds.$[].locations': {
$elemMatch: {
uniqueName,
},
},
},
],
},
{
$pull: {
//#ts-ignore
'feeds.$[].locations': { uniqueName },
},
},
{ new: true }
);

How to extract number from a string in mongodb query?

The only problem with commented query is that "$DAMAGE_PROPERTY" is written in 0K, 1K, 250K, 5.20M format, and the $todecimal/$toDouble aren't able to convert that to an integer. My purpose is to extract number out of string in the query itself and not iterate over the result array.
try {
const db = client.db(dbName);
let collection = db.collection("Storm");
let query = {
STATE: "INDIANA",
EVENT_TYPE: event,
DAMAGE_PROPERTY: { $nin: ["", null] },
YEAR: { $gte: startYear, $lte: endYear },
};
// --> aggregation failed.
// let res = await collection
// .aggregate([
// { $match: query },
// {
// $project: {
// document: "$$ROOT",
// damage: {
// $sum: {
// $toDouble: "$DAMAGE_PROPERTY", //Cause of Error: no conversion
// },
// },
// },
// },
// ])
// .toArray();
// console.log(res);
// ---> Solution I want to avoid
let res = await collection.find(query).toArray();
var totalDamage = 0;
for (let i = 0; i < res.length; i++) {
totalDamage += parseFloat(res[i].DAMAGE_PROPERTY);
}
console.log(
`Total Damage to property in Indiana from ${startYear} to ${endYear} due to ${event} is ${totalDamage}K`
);
} catch (err) {
console.log(err);
} finally {
client.close();
}
Test Data in json:
{
"BEGIN_YEARMONTH": 200809,
"BEGIN_DAY": 14,
"BEGIN_TIME": 830,
"END_YEARMONTH": 200809,
"END_DAY": 14,
"END_TIME": 1030,
"EPISODE_ID": 21247,
"YEAR": 2008,
"DEATHS_DIRECT": 0,
"DEATHS_INDIRECT": 0,
"DAMAGE_PROPERTY": "5.20M",
},
Providing another solution to avoid clutter/confuse, Answer #1:
db.collection.aggregate([
{
$group: {
_id: null,
DAMAGE_PROPERTY: {
$push: {
$let: {
vars: {
damage: {
$toDouble: {
$substrBytes: ["$DAMAGE_PROPERTY", 0, { $subtract: [{ $strLenCP: "$DAMAGE_PROPERTY" }, 1] }]
}
},
unit: {
$substrBytes: ["$DAMAGE_PROPERTY", { $subtract: [{ $strLenCP: "$DAMAGE_PROPERTY" }, 1] }, 1]
}
},
in: {
$switch: {
branches: [
{
case: { $eq: ["$$unit", "M"] },
then: {
$multiply: ["$$damage", 100]
}
},
{
case: { $eq: ["$$unit", "K"] },
then: {
$multiply: ["$$damage", 10]
}
},
// You can have more cases in future
],
default: "Error: No unit matched." // or 0
}
}
}
}
}
}
},
{
$project: {
_id: 0,
totalDamage: { $sum: "$DAMAGE_PROPERTY" }
}
}
]);
Output:
{
"totalDamage" : 615
}
UPDATE based on coments:
db.collection.aggregate([
{
$group: {
_id: null,
DAMAGE_PROPERTY: {
$push: {
$cond: {
if: { $lt: [{ $size: { $split: ["$DAMAGE_PROPERTY", "K"] } }, 2] },
then: {
$multiply: [
{
$toDouble: { $first: { $split: ["$DAMAGE_PROPERTY", "M"] } }
},
100
]
},
else: {
$multiply: [
{
$toDouble: { $first: { $split: ["$DAMAGE_PROPERTY", "K"] } }
},
10
]
}
}
}
}
}
},
{
$project: {
_id: 0,
totalDamage: { $sum: "$DAMAGE_PROPERTY" }
}
}
]);
Output for the updated query
{
"totalDamage" : 615
}
What is $cond doing?
STEP 1: Try to split DAMAGE_PROPERTY by "K".
// Example 1: split_result = ["5.2M"]
// Example 2: split_result = ["9.5", ""]
if the length of split_result array is less than 2:
Try to split DAMAGE_PROPERTY by "M"
// For example: split_result = ["5.2", ""]
typecast the first string to decimal
return the result
else:
Split the DAMAGE_PROPERTY by "K"
// For example: split_result = ["9.5", ""]
typecast the first string to decimal
return the result
Try this:
db.collection.aggregate([
{
$group: {
_id: null,
DAMAGE_PROPERTY: {
$push: {
$toDouble: {
$cond: {
if: { $lt: [{ $size: { $split: ["$DAMAGE_PROPERTY", "K"] } }, 2] },
then: { $first: { $split: ["$DAMAGE_PROPERTY", "M"] } },
else: { $first: { $split: ["$DAMAGE_PROPERTY", "K"] } }
}
}
}
}
}
},
{
$project: {
_id: 0,
totalDamage: { $sum: "$DAMAGE_PROPERTY" }
}
}
]);
Output:
{
"totalDamage" : 14.7
}
Test data:
/* 1 createdAt:3/12/2021, 3:22:08 PM*/
{
"_id" : ObjectId("604b39c84b5860176c2254e2"),
"DAMAGE_PROPERTY" : "5.20M"
},
/* 2 createdAt:3/12/2021, 3:22:08 PM*/
{
"_id" : ObjectId("604b39c84b5860176c2254e3"),
"DAMAGE_PROPERTY" : "9.5K"
}

Retrieve data inside nested array in MongoDB and paginate aggregation show all docs

If there are following documents inside a collection:
{
"_id": "8048d05478813e439442abac",
"projectName": "Y-10000-A",
"archived": false,
"tabs":
[
{
"visible": true,
"permisos": ["604892e9266fc72b04be62f0", "12312131b04be62f0"]
},
{
"visible": true,
"permisos": ["12312131b04be62f0"]
}
],
},
{
"_id": "9048d05478813e439442abad",
"projectName": "Y-10000-B",
"archived": false,
"tabs": [{
"visible": true,
"permisos": ["12312131b04be62f0"]
}]
},
{
"_id": "9048d05478813e439442abae",
"projectName": "Y-10000-C",
"archived": true,
"tabs": [{
"visible": true,
"permisos": ["604892e9266fc72b04be62f0"]
}]
},
I want to filter all the projects that have at least one tab matching the user._id in the permisos array. Also I am filtering by archived and by text. The text search isn't working properly.
const projectsAggregation = await projectSchema.aggregate([
{ $match: { $text: { $search: "Y" } } },
{ $match : { archived: false} },
{
$addFields: {
tabs: {
$filter: {
input: "$tabs",
as: "tab",
cond: { $in: [user._id, "$$tab.permisos"] }
}
}
}
},
{
$match: {
$expr: {
$gt: [{ $size: "$tabs" }, 0]
}
}
}
const projectsDocs = await projectSchema.aggregatePaginate(projectsAggregation, {
limit: pagination.limit ? parseInt(pagination.limit) : 10,
page: pagination.page ? parseInt(pagination.page) + 1 : 1
});
I want to paginate the aggregation. I am using mongoose-aggregate-paginate-v2. It returns all the documents of the schema. It should return only the documents from projectsAggregation. Also the
{ $match: { $text: { $search: "Y" } } }, it doesn't work.
Raw query:
let user_id = "604892e9266fc72b04be62f0";
let regex = /^Y/
db.projects.aggregate([
{
$match: {
archived: false,
projectName: {
$regex: regex,
$options: 'i'
}
}
},
{
$addFields: {
tabs: {
$filter: {
input: "$tabs",
as: "tab",
cond: { $in: [user_id, "$$tab.permisos"] }
}
}
}
},
{
$match: {
$expr: {
$gt: [{ $size: "$tabs" }, 0]
}
}
}
]);
Using Mongoose:
File: project_schema.js
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-aggregate-paginate-v2');
let projectSchema = new Schema({
// Put your schema here
},
{
collection: 'projects'
});
projectSchema.plugin(mongoosePaginate); // <- This step is important!
module.exports = mongoose.model('projects', projectSchema);
File: project_manager.js
let user_id = "604892e9266fc72b04be62f0";
let regex = /^Y/
const projectAggQuery = projectSchema.aggregate([
{
$match: {
archived: false,
projectName: {
$regex: regex,
$options: 'i'
}
}
},
{
$addFields: {
tabs: {
$filter: {
input: "$tabs",
as: "tab",
cond: { $in: [user_id, "$$tab.permisos"] }
}
}
}
},
{
$match: {
$expr: {
$gt: [{ $size: "$tabs" }, 0]
}
}
}
]);
const projectsDocs = await projectSchema.aggregatePaginate(projectAggQuery, {
limit: pagination.limit ? parseInt(pagination.limit) : 10,
page: pagination.page ? parseInt(pagination.page) + 1 : 1
});

mongodb aggregation matching properties

I have a user_status collection and its schema is like this
const userStatus = mongoose.model(
'user_status',
new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'user'
},
isActive: {
type: Boolean,
default: false
}
},
{ timestamps: true }
)
);
I need to get all the active users count for a given specific month (1, 2, etc..)
I tried this. But this snippet is not giving expected output
// get all active user count for a specific month
router.post('/report', async (req, res) => {
const selectedMonth = req.body.month;
console.log('month', selectedMonth);
const usersStatus = await UserStatus.aggregate([
{ $project: { month: { $month: '$updatedAt' } } },
{
$match: { $and: [{ month: { $eq: selectedMonth } }, { isActive: true }] }
},
{ $group: { _id: '$user', count: { $sum: 1 } } }
]).exec();
res.status(200).send(usersStatus);
});
Could you please tell me where I'm wrong?
You have vanished the isActive and user field after the first $project stage
You can use below aggregation
const usersStatus = await UserStatus.aggregate([
{ "$addFields": { "month": { "$month": "$updatedAt" } } },
{ "$match": { "month": selectedMonth, "isActive": true }},
{ "$group": { "_id": "$user", "count": { "$sum": 1 } } }
])
Or even more convenient way using $expr
const usersStatus = await UserStatus.aggregate([
{ "$match": {
"$expr": {
"$and": [
{ "$eq": [{ "$month": "$updatedAt" }, selectedMonth] },
{ "$eq": ["$isActive", true] }
]
}
}},
{ "$group": { "_id": "$user", "count": { "$sum": 1 } } }
])