Filter Implementation in Mongodb - database

I have three collections,
collection 1: subjects
{
_id: ObjectId(),
name: 'Subject Name',
...
}
collection 2: filters
{
_id: ObjectId(),
filterName: 'Filter One',
...
}
collection 3: subject_filters
{
_id: ObjectId(),
subjectId: {
ref: 'subjects'
},
filterId: {
ref: 'filters'
},
...
}
Now I am creating a UI where user can view subjects by filters, where they can select multiple filters, If no filters were selected then all subjects will be returned, there's also a possiblity that some subjects are not present in subject_filters but they should appear in the UI, if no filters were selected. How to achive this. I have all the ids of filters seleced by user in an array castedFilterIds (can be empty), What I have tried is
db.subjects_filter.aggregate([
{
$match: {filterId: {$in: castedFilterIds}} // I managed to add this conditionally, if castedFilterIds length is > 0
},
{
$group: {
_id: '$subjectId',
doc: {$first: '$$ROOT'},
}
},
{
$replaceRoot: {
newRoot: '$doc',
}
},
])
This is returning only those subjects which are associcated with filters, but I need to query on all subjects. Any help will be appriciated. Thanks in advance.

Related

Query to aggregate a field from one collection to another in MongoDB

I have these two collections.
collectionname : req
{
reqId: "A123",
status: "1",
location: "hyd"
}
collection name : reqUser
{
req: "A123"
userId: "U1787"
designation: "employee"
}
Need an aggregate filtering the req collection based on location as hyd & aggregating the user field based on reqId.
Need Like this:
{
reqId: "A123",
status: "1",
userId: "U1787"
}
I used query on req collection with $match to filter based on location & $project to display only required fields from req collection and $lookup to match the reqId's from both collection but the issue is I am getting the whole reqUser object.
I am getting Something like this:
{
reqId: "A123",
status: "1",
reqUser:[
{
req: "A123"
userId: "U1787"
designation: "employee"
}
]
}
I need only userId field. Can anyone help me with obtaining the data as per my requirement mentioned above.
I am using mongo version 3.4.
Assuming req and reqUser has one-to-one relations, you could use the following query:
db.req.aggregate([
{
"$lookup": {
"from": "reqUser",
"localField": "reqId",
"foreignField": "req",
"as": "user"
}
},
{
$set: {
userId: {
$arrayElemAt: [
"$user.userId",
0
]
}
}
},
{
"$project": {
user: 0
}
}
])
Mongo playground ref: https://mongoplayground.net/p/V7bUdOXjnFC

Exclude field from a document based on another field value mongodb mongoose

I have a users schema in which there is a companies field which I only want to select if role field value is admin else it should not be returned in find query.
user Schema:
const userInfoSchema = new mongoose.Schema({
...,
companies: [
{
type: mongoose.Schema.ObjectId,
ref: 'companyinfos',
},
],
role: {
type: String,
enum: ['user', 'admin', 'employee'],
default: 'user',
},
});
I have tried to solve this by using pre find hook but was unable to exclude the companies field.
userInfoSchema.post(/^find/, function (doc, next) {
if (doc.role !== 'admin') {
this.find({}).select('-companies');
}
next();
});
Or is there any way to conditionally set select in the companies field in the userInfoSchema based on the role value?
Please help.
use aggregate
db.collection.aggregate([
{
"$project": {
companies: {
"$cond": {
"if": {
$eq: [
"$role",
"admin"
]
},
"then": "$companies",
"else": 0
}
}
}
},
{
$match: {
companies: {
$ne: 0
}
}
}
])
https://mongoplayground.net/p/I8HGvz6h7MP

Mongo - add field if object in array of sub docs has value

Details
I develop survey application with express and struggle with some getting of data.
The case:
you can get all surveys by "GET /surveys". And every survey doc has to contains hasVoted:mongoose.Bool and optionsVote:mongoose.Map if the user has voted for the survey. (SurveySchema is bellow)
you can vote for survey by "POST /surveys/vote"
you can see the results of any survey only if you vote for it
new Schema({
question: {
type: mongoose.Schema.Types.String,
required: true,
},
options: {
type: [{
type: mongoose.Schema.Types.String,
required: true,
}]
},
optionsVote: {
type: mongoose.Schema.Types.Map,
of: mongoose.Schema.Types.Number,
},
votesCount: {
type: mongoose.Schema.Types.Number,
},
votes: {
type: [{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
option: mongoose.Schema.Types.Number,
}]
},
})
Target:
So the target of the question is how to add fields hasVoted and optionsVote if there is "Vote" sub document in votes array where user===req.user.id ?
I believe you got the idea so if you have an idea how to change the schema to achieve the desired result I'm open!
Example:
Data:
[{
id:"surveyId1
question:"Question",
options:["op1","op2"],
votes:[{user:"userId1", option:0}]
votesCount:1,
optionsVote:{"0":1,"1":0}
},{
id:"surveyId2
question:"Question",
options:["op1","op2"],
votes:[{user:"userId2", option:0}]
votesCount:1,
optionsVote:{"0":1,"1":0}
}]
Route handler:
Where req.user.id='userId1' and then make the desired query.
The result
[{ // Voted for this survey
id:"surveyId1
question:"Question",
options:["op1","op2"],
votes:[{user:"userId1", option:0}]
votesCount:1,
optionsVote:{"0":1,"1":0},
hasVoted:true,
},{ // No voted for this survey
id:"surveyId2
question:"Question",
options:["op1","op2"],
votesCount:1,
}]
In MongoDB, you can search for sub document as follows
//Mongodb query to search for survey filled by a user
db.survey.find({ 'votes.user': myUserId })
So with this when you can get results only where user has voted, do you really need hasVoted field?
To have optionsVote field, first I would prefer schema of optionsVote as {option: "a", count:1}. You can choose any of the following approach.
A. manage to update optionsVote field at the time of update by incrementing the count of the voted option when you POST /survey/vote.
B. Another approach would be to calculate the optionsVote based on votes entries at the time of GET /survey. You can do this via aggregate
//Mongodb query to get optionsVote:{option: "a", count:1} from votes: { user:"x", option:"a"}
db.survey.aggregate([
{ $unwind: "$votes" },
{ $group: {
"_id": { "id": "_id", "option": "$votes.option" },
optionCount: { $sum: 1 }
}
},
{
$group: { "_id": "$_id.id" },
optionsVote: { $push : { option: "$_id.option", count: "$optionCount" } },
votes: { $push : '$votes'}
}
])
//WARNING: I haven't tested this query, this is just to show the approach -> group based on votes.option and count all votes for that option for each document and then create optionsVote field by pushing all option with their count using $push into the field `optionsVote`
I recommend approach A because I assume POST operations would be quite less than GET operations. Also it's easier to implement. Having said that, keeping query in B handy will help you with sanity check.

MongoDB query into an array of arrays

I have the following schema into my DB:
{ name: 'AFG',
documents: [
{ name: 'doc1',
templates: [
{ name: 'templ1',
lastModified: <Date>},
...]
},
...]
}
I am trying to make a query to look for the template with name 'templ1'. If I find it, I have to compare the last modification date with another and update the value. If I don't find it, I have to push it into the array.
I have tried to do it with the following query:
Country.findOne({name : countrySelected, documents : {$elemMatch: {name: documentSelected}}, 'documents.templates' : {$elemMatch: {name: templateSelected}}}, function(err, templateFound){...}
But I get the following error:
MongoError: cannot use the part (documents of documents.templates) to traverse the element ({documents: [ { name: "Passport", templates: [] }, { name: "Visa", templates: [] }, { name: "Driver's Licence", templates: [] }, { name: "Id Card", templates: [] }, { name: "Other", templates: [] } ]})
Someone knows how can I do it? Thanks!!
You have to use the $in operator to search in arrays.
Country.findOne({
name : countrySelected,
documents: {
$elemMatch: {
$and: [{
name: {
$in: documentSelected //<-- must be array like ['doc1']
}
}, {
'templates.name': {
$in: documentSelected //<-- must be array like ['tmpl1']
}
}]
}
}
}, function(err, templateFound){
//do something with err and templateFound
});
To update the LastModified date you need to update it in the callback function and save the document.
See this answer for nested array updates

Filtering an embedded array in MongoDB

I have a Mongodb document that contains an an array that is deeply imbedded inside the document. In one of my action, I would like to return the entire document but filter out the elements of that array that don't match that criteria.
Here is some simplified data:
{
id: 123 ,
vehicles : [
{name: 'Mercedes', listed: true},
{name: 'Nissan', listed: false},
...
]
}
So, in this example I want the entire document but I want the vehicles array to only have objects that have the listed property set to true.
Solutions
Ideally, I'm looking for a solution using mongo's queries (e.g. `$unwind, $elemMatch, etc...) but I'm also using mongoose so solution that uses Mongoose is OK.
You could use aggregation framework like this:
db.test312.aggregate(
{$unwind:"$vehicles"},
{$match:{"vehicles.name":"Nissan"}},
{$group:{_id:"$_id",vehicles:{$push:"$vehicles"}}}
)
You can use $addToSet on the group after unwinding and matching by listed equals true.
Sample shell query:
db.collection.aggregate([
{
$unwind: "$vehicles"
},
{
$match: {
"vehicles.listed": {
$eq: true
}
}
},
{
$group: {
_id: "$id",
vehicles: {
"$addToSet": {
name: "$vehicles.name",
listed: "$vehicles.listed"
}
}
}
},
{
$project: {
_id: 0,
id: "$_id",
vehicles: 1
}
}
]).pretty();

Resources