Generate new field in $project from the value of another - mongodb

I have a collection of users with a field named level with a numeric value of 0 to 3. I am trying to return a generated field with the textual representation of the user's level. So far I have this, but it replaces the level field.
{ $project: {
'_id': 1,
'name': 1,
'email': 1,
'username': 1,
'password': 1,
'registered': 1,
'level': {
$switch: {
branches: [
{ case: 0, then: 'Pending' },
{ case: 1, then: 'Regular' },
{ case: 2, then: 'User manager' },
{ case: 3, then: 'Administrator' }
],
default: 'Unknown'
}
},
}
I would like to have a field named levelName in my aggregation output. How do I do this? I tried this:
'levelName': {
$switch: {
branches: [
{ case: { level: 0 }, then: 'Pending' },
{ case: { level: 1 }, then: 'Regular' },
{ case: { level: 2 }, then: 'User manager' },
{ case: { level: 3 }, then: 'Administrator' }
],
default: 'Unknown'
}
},
but to no avail.

In projection, you can easily change field name. For example, in your data, if you want to change your name of level field to levelName, you can simply write new name in field and assign value of old fieldName using $fieldName.
{
$project: {
levelName: "$level" //new name is levelName, and is assigned value of original field name level.
}
}
Now, in your case, you can achieve this by above way, However, your $switch statement is invalid or you are using $switch in wrong way. You need to specify which field to compare in your case expression i.e. { case: {$eq:["$level", 0]}, then: 'Pending' } instead of { case: 0, then: 'Pending' }.
db.indexName.aggregate([
{
$project:
{
'_id': 1,
'name': 1,
'email': 1,
'username': 1,
'password': 1,
'registered': 1,
levelName: {
$switch: {
branches: [
{ case: {$eq:["$level", 0]}, then: 'Pending' },
{ case: {$eq:["$level", 1]}, then: 'Regular' },
{ case: {$eq:["$level", 2]}, then: 'User manager' },
{ case: {$eq:["$level", 3]}, then: 'Administrator' }
],
default: 'Unknown'
}
}
}
}
])

Related

How to get data with a cleaner way using mongoose?

I'm filtering the data based on a Boolean savedBoolean , and if that Boolean is not being inputted I'm getting all the data, this code works for now. But how to do it in a cleaner way since I'm duplicating the code.
let filteredReviews : any | undefined;
if (savedBoolean === true || savedBoolean === false) {
filteredReviews = await Interviewee.aggregate([{
$project: {
_id: 0,
userId: 1,
'interviews.review': 1,
},
},
{
$unwind: '$interviews',
},
{
$match: {
userId: '4',
'interviews.review.saved': savedBoolean,
},
},
{
$group: {
_id: '$interviews.review._id',
review: {
$first: '$interviews.review',
},
},
},
]).skip((Number(page) - 1) * 3).limit(3);
}
if (savedBoolean === undefined) {
filteredReviews = await Interviewee.aggregate([{
$project: {
_id: 0,
userId: 1,
'interviews.review': 1,
},
},
{
$match: {
userId: '4',
},
},
{
$unwind: '$interviews',
},
]).skip((Number(page) - 1) * 3).limit(3);
}
In MongoDB, the db.collection.remove() method removes documents from a collection. You can remove all documents from a collection, remove all documents that match a condition, or limit the operation to remove just a single document.

How can i limit each ID i pass to $in operator inside the $match stage to only 4 elements

I have an aggregate like this :
const files = await File.aggregate([
{
$match: { facilityId: { $in: facilities } }
},
{
$sort: { createdAt: 1 }
},
{
$project: {
file: 0,
}
}
])
And i would like to have each "facility" return only 4 files, i used to do something like facilities.map(id => query(id)) but id like to speed things up in production env.
Using $limit will limit the whole query, that's not what i want, i tried using $slice in the projection stage but got en error :
MongoError: Bad projection specification, cannot include fields or add computed fields during an exclusion projection
how can i achieve that in a single query ?
Schema of the collections is :
const FileStorageSchema = new Schema({
name: { type: String, required: true },
userId: { type: String },
facilityId: { type: String },
patientId: { type: String },
type: { type: String },
accessed: { type: Boolean, default: false, required: true },
file: {
type: String, //
required: true,
set: AES.encrypt,
get: AES.decrypt
},
sent: { type: Boolean, default: false, required: true },
},
{
timestamps: true,
toObject: { getters: true },
toJSON: { getters: true }
})
And i would like to returns all fields except for the file fields that contains the encrypted blob encoded as base64.
Also: i have the feeling that my approach is not correct, what i really would like to get is being able to query all facilityId at once but limited to the 4 latest file created for each facility, i though using an aggregate would help me achieve this but im starting to think it's not how its done.
From the question the schema is not clear. So I have two answers based on two Schemas. Please use what works for you
#1
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$group: {
_id: "$facilityId",
files: {
$push: "$file"
}
}
},
{
$project: {
files: {
$slice: [
"$files",
0,
4
],
}
}
}
])
Test Here
#2
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$project: {
facilityId: 1,
file: {
$slice: [
"$file",
4
]
}
}
}
])
Test Here

MongoDB query - unwind and match preserving null OR different value/ add a new field based on a condition

If a have a following structure :
{
_id: 1,
name: 'a',
info: []
},
{
_id: 2,
name: 'b',
info: [
{
infoID: 100,
infoData: 'my info'
}
]
},
{
_id: 3,
name: 'c',
info: [
{
infoID: 200,
infoData: 'some info 200'
},
{
infoID: 300,
infoData: 'some info 300'
}
]
}
I need to query in such a way to obtain the documents where infoID is 100 showing the infoData, or nothing if info is empty, or contains subdocuments with infoID different from 100.
That is, I would want the following output:
{
_id: 1,
name: 'a',
infoData100: null
},
{
_id: 2,
name: 'b',
infoData100: 'my info'
},
{
_id: 3,
name: 'c',
infoData100: null
}
If I $unwind by info and $match by infoID: 100, I lose records 1 and 3.
Thanks for your responses.
Try below query :
Query :
db.collection.aggregate([
/** Adding a new field or you can use $project instead of addFields */
{
$addFields: {
infoData100: {
$cond: [
{
$in: [100, "$info.infoID"] // Check if any of objects 'info.infoID' has value 100
},
{
// If any of those has get that object & get infoData & assign it to 'infoData100' field
$let: {
vars: {
data: {
$arrayElemAt: [
{
$filter: {
input: "$info",
cond: { $eq: ["$$this.infoID", 100] }
}
},
0
]
}
},
in: "$$data.infoData"
}
},
null // If none has return NULL
]
}
}
}
]);
Test : MongoDB-Playground

MongoDb find by value in array

here is the model of my collection :
classes:[{
class:String,
info:{
subjects:[String],
students:[{
name:String,
surname:String,
matriculae:Number,
path_1:String,
path_2:String
}],
classTeacher:{
name:String,
surname:String
}
}
}],
accademicYear:String}];
I'd like to retrive value 'matriculae' given the name,surname and accademicYear of a student. I cant wrap my head 'round it tho! Thanks for help.
If you mean you want the result flat format, try this:
School.aggregate([
{
$unwind: '$classes'
}, {
$project: {
accademicYear: 1,
students: "$classes.info.students"
}
}, {
$unwind: "$students"
}, {
$project: {
accademicYear: 1,
matriculae: "$students.matriculae",
name: "$students.name",
surname: "$students.surname",
}
}
])
In case of the classes is collection and accademicYear is inside of the classes collection.Plus added the match criteria.
db.getCollection('classes').aggregate([{
$project: {
accademicYear: 1,
students: "$info.students"
}
}, {
$unwind: "$students"
}, {
$project: {
accademicYear: 1,
matriculae: "$students.matriculae",
name: "$students.name",
surname: "$students.surname",
}
}, {
$match: {
name: name,
surname: surname,
accademicYear: year
}
}])

Change capital letters in mongo to camel casing?

I have a collection named User, which contains the the fields firstName and secondName. But the data is in capital letters.
{
firstName: 'FIDO',
secondName: 'JOHN',
...
}
I wanted to know whether it is possible to make the field to camel case.
{
firstName: 'Fido',
secondName: 'John',
...
}
You can use a helper function to get your desired answer.
function titleCase(str) {
return str.toLowerCase().split(' ').map(function(word) {
return word.replace(word[0], word[0].toUpperCase());
}).join(' ');
}
db.User.find().forEach(function(doc){
db.User.update(
{ "_id": doc._id },
{ "$set": { "firstName": titleCase(doc.firstName) } }
);
});
Run an update operation with aggregate pipeline as follows:
const titleCase = key => ({
$concat: [
{ $toUpper: { $substrCP: [`$${key}`,0,1] } },
{ $toLower: {
$substrCP: [
`$${key}`,
1,
{ $subtract: [ { $strLenCP: `$${key}` }, 1 ] }
]
} }
]
});
db.User.updateMany(
{},
[
{ $set: {
firstName: titleCase('firstName'),
secondName: titleCase('secondName')
} }
]
)
Mongo Playground