use a variable value in lookup mongoose expression - node.js

I am using a mongoose model that brings documents and I add an aggregate to "join" with other collection. The real problem is when I use aggregate I cannot filter documents in the "original" collection (similar to findOne()). So when I use the aggregate function I get all documents but I just need 1.
i.e. (this is only an example for a point describe)
Collection A = {id,name,birthdate,dni}
Collection B = {avatar,dni.....}
a.aggregate([ '$lookup': {
'from': 'avatars',
'localField': 'dni',
'foreignField': 'dni',
'as': 'aditionalinfo'
} ])
this result in a full collection A + additionalinfo object from B for each document but supposed to bring one person from collection A with the additional data (additionalinfo). I mean, I need to bring one document "joined" with the corresponding document from the other collection.
here is the last code I tried
let ley = await leyes.aggregate([
{
'$lookup': {
'from': 'digessituacion',
'localField': 'idsituacio',
'foreignField': 'idsituacio',
'as': 'situacion'
}
}, {
'$lookup': {
'from': 'digescategoria',
'localField': 'catego',
'foreignField': 'catego',
'as': 'categoria'
}
},
{
'$match': {
'ley': 8996 // this is the number which I need to set variable cause is passed from params
}
}]);
this is the schema
import { Schema, model } from "mongoose";
const leyesSchema = new Schema(
{
ley: Number,
resumen: String,
notas: String,
txtley: String,
catego: String,
detcatego: String,
idsituacio: Number,
promulgada: Date,
sancionada: Date,
},
{ timestamps: false, versionKey: false }
);
export default model("leyes", leyesSchema);
and the Schema of collection needed to "join"
import { Schema, model } from "mongoose";
const digessituacionSchema = new Schema(
{
idsituacio: Number,
situacion: String,
},
{ timestamps: false, versionKey: false }
);
export default model("digessituacion", digessituacionSchema);
if I put the number manually works but is not usable cause it must be a variable. Anyone knows the way for do this? It is possible?

When the req.params is parsed, they are always parsed as type string. Since your ley field is of type number, you have to parse it first:
"$match": {
"ley": parseInt(req.params.ley, 10)
}

Related

Update Mongoose Array

so basically I have this and I am trying to update the STATUS part of an array.
However, everything I try does nothing. I have tried findOneAndUpdate also. I am trying to identify the specific item in the array by the number then update the status part of that specific array
(Sorry for formatting, I have no idea how to do that on the site yet ...) (Full code can be found here: https://sourceb.in/0811b5f805)
Code
const ticketObj = {
number: placeholderNumber,
userID: message.author.id,
message: m.content,
status: 'unresolved'
}
let tnumber = parseInt(args[1])
let statuss = "In Progress"
await Mail.updateOne({
"number": tnumber
}, { $set: { "status": statuss } })
Schema
const mongoose = require('mongoose')
const mailSchema = new mongoose.Schema({
guildID: { type: String, required: true },
ticketCount: { type: Number, required: true },
tickets: { type: Array, default: [] }
}, { timestamps: true });
module.exports = mongoose.model('Mail', mailSchema)
You need to use something like Mail.updateOne({"guildID": message.guild.id}, {$set: {`tickets.${tnumber}.status`: statuss}})
or for all objects in array:
Mail.updateOne({"guildID": message.guild.id}, {$set: {'tickets.$[].status': statuss}})
Also, you need to create a schema for the tickets, as it is described in docs:
one important reason to use subdocuments is to create a path where there would otherwise not be one to allow for validation over a group of fields

Change a single value of a subdocument in mongodb

I have a Model called Notes. It has a subdocument requests which holds various documents with values userId, reqType, accepted value( false by default) and noteId of the sender, request and note respectively. When the user hits a certain route I want to keep all the data to be as their previous values, just updating the accepted field to true.
The below code leads to no change in the data or a different iteration leads to erasing all the data other than accepted field and modifying it to true.
How should I do this?
const noteSchema = new mongoose.Schema(
requests: [
{
userId: mongoose.Schema.ObjectId,
noteId: mongoose.Schema.ObjectId,
reqType: String,
accepted: {
type: Boolean,
default: false,
},
},
],
}
)
const Note = mongoose.model('Note', noteSchema)
const note = await Note.findById(req.body.noteId)
await note.updateOne({
requests: {
$elemMatch: {
userId: req.body.userId,
reqType: req.body.reqType,
noteId: req.body.noteId,
},
$set: { "requests.$.accepted": true },
},
})
You do not need to retrieve the document and then update it. Just update it. Use this one:
await Note.updateOne(
{
"requests.userId": req.body.userId,
"requests.reqType": req.body.reqType,
"requests.noteId": req.body.noteId
},
{
$set:
{
"requests.$.accepted":true
}
}
);
I checked, it worked.
With first part mongoose will find the document, with $set it will be updated.

MongoDB - update data in array of objects within object

I have a document in mongoDB structured like that
_id: ObjectId("generatedByMongo"),
name: {
required: true,
type: String,
trim: true
},
last: {
required: true,
type: String,
trim: true
},
grades: [{
grade: {
_id: ObjectId(""),
grade: Number,
date: date
}
}]
And to server I send array of objects containing 3 fields
[
{studentId}, {gradeId}, {newGrade}
]
What I'm trying to accomplish is I want to find in within that user collection grade with given gradeId and update it's value to newGrade. As far as I tried to do that I have done this
router.patch('/students/updateGrade',async(req,res) => {
const studentId = req.body.updateGradeArray[0].studentId;
const gradeId = req.body.updateGradeArray[0].gradeId;
const newGrade = req.body.updateGradeArray[0].newGrade;
try {
const student = await Student.find({_id: studentId})
.select({'grades': {$elemMatch: {_id: gradeId}}});
} catch(e) {
console.log(e);
}
}
);
If you intend to update just grade.grade(the number value), try this:
Student.updateOne(
// Find a document with _id matching the studentId
{ "_id": studentId },
// Update the student grade
{ $set: { "grades.$[selectedGrade].grade": newGrade } },
{ arrayFilters: [{ "selectedGrade._id": gradeId }] },
)
Why this should work:
Since you are trying to update a student document, you should be using one of MongoDB update methods not find. In the query above, I'm using the updateOne method. Inside the updateOne, I am using a combination of $set and $[identifier] update operators to update the student grade.
I hope this helps✌🏾

populate with condition in mongoose

I have a movie booking data like below
movie order schema
{
"movie_id": "5d64fb7975214a183bf10f5b",
"variant_id": "5d64fda8fc7f911a77afd55c",
}
and movie schema data like below
{
"_id":"5d64fb7975214a183bf10f5b",
"description":"Sahoo movie ",
"options":[
],
"variants":[
{
"enabled":true,
"_id":"5d64fda8fc7f911a77afd55c",
"variant_name":"",
"regular_price":345,
"sale_price":125,
"stock_quantity":45,
},
{
"enabled":true,
"_id":"5d661c8181a4572a27f048dd",
"variant_name":"",
"regular_price":120,
"sale_price":50,
"stock_quantity":10,
}
],
"on_sale":false,
"variable":true
}
now I'm trying to querying the movie order
let data = await MovieOrder.find().populate(movie_id)
but it is giving movie details with all the variant.but what I'm looking for here is
In the movie order what is the variant_id is present based on that I need to populate the movie with variant based on that variant id on the movie order
Note: the result should be, what are the variant_id in the movie order schema is equal to variant id in the movie schema
Hope you guys understand my problem, please give me the solution
With the way your schema is designed it is hard for populate to filter the movies variants array with the variant_id in the movies order
as this is not how populate works.
In order to use populate properly, you would have to change the movies schema making the variants array as ref to
Variants model. For example, your schema definitions would need to look like
Schema definitions
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const variantSchema = new Schema({
enabled: Boolean,
variant_name: String,
regular_price: Number,
sale_price: Number,
stock_quantity: Number
})
const Variant = mongoose.model('Variant', variantSchema)
const movieSchema = new Schema({
description: String,
options: [String],
variants: [{ type: 'ObjectId', ref: 'Variant' }],
on_sale: Boolean,
variable: Boolean
})
const Movie = mongoose.model('Movie', movieSchema)
await MovieOrder.find().populate('movie_id variant_id')
This way your query just returns the movie and the variant it needs.
However, if the current schema design remains as it is, you can use $lookup in an aggregate pipeline to do the populate and then filter the resulting array using $filter on the variant that matches the variant_id field in your MovieOrder model:
await MovieOrder.aggregate([
{ '$lookup': {
'from': 'movies',
'localField': "movie_id", // field in the movieorders collection
'foreignField': "_id", // field in the movies collection
'as': "movie"
} },
{ '$addFields': {
'movie': { '$arrayElemeAt': ['$movie', 0 ] }
} },
{ '$addFields': {
'movie.variants': {
'$filter': {
'input': '$movie.variants',
'cond': { '$eq': ['$variant_id', '$$this._id'] }
}
}
} },
]).exec()

How to join two collections in mongoose

I have two Schema defined as below:
var WorksnapsTimeEntry = BaseSchema.extend({
student: {
type: Schema.ObjectId,
ref: 'Student'
},
timeEntries: {
type: Object
}
});
var StudentSchema = BaseSchema.extend({
firstName: {
type: String,
trim: true,
default: ''
// validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
type: String,
trim: true,
default: ''
// validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
displayName: {
type: String,
trim: true
},
municipality: {
type: String
}
});
And I would like to loop thru each student and show it's time entries. So far I have this code which is obviously not right as I still dont know how do I join WorksnapTimeEntry schema table.
Student.find({ status: 'student' })
.populate('student')
.exec(function (err, students) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
_.forEach(students, function (student) {
// show student with his time entries....
});
res.json(students);
});
Any one knows how do I achieve such thing?
As of version 3.2, you can use $lookup in aggregation pipeline to perform left outer join.
Student.aggregate([{
$lookup: {
from: "worksnapsTimeEntries", // collection name in db
localField: "_id",
foreignField: "student",
as: "worksnapsTimeEntries"
}
}]).exec(function(err, students) {
// students contain WorksnapsTimeEntries
});
You don't want .populate() here but instead you want two queries, where the first matches the Student objects to get the _id values, and the second will use $in to match the respective WorksnapsTimeEntry items for those "students".
Using async.waterfall just to avoid some indentation creep:
async.waterfall(
[
function(callback) {
Student.find({ "status": "student" },{ "_id": 1 },callback);
},
function(students,callback) {
WorksnapsTimeEntry.find({
"student": { "$in": students.map(function(el) {
return el._id
})
},callback);
}
],
function(err,results) {
if (err) {
// do something
} else {
// results are the matching entries
}
}
)
If you really must, then you can .populate("student") on the second query to get populated items from the other table.
The reverse case is to query on WorksnapsTimeEntry and return "everything", then filter out any null results from .populate() with a "match" query option:
WorksnapsTimeEntry.find().populate({
"path": "student",
"match": { "status": "student" }
}).exec(function(err,entries) {
// Now client side filter un-matched results
entries = entries.filter(function(entry) {
return entry.student != null;
});
// Anything not populated by the query condition is now removed
});
So that is not a desirable action, since the "database" is not filtering what is likely the bulk of results.
Unless you have a good reason not to do so, then you probably "should" be "embedding" the data instead. That way the properties like "status" are already available on the collection and additional queries are not required.
If you are using a NoSQL solution like MongoDB you should be embracing it's concepts, rather than sticking to relational design principles. If you are consistently modelling relationally, then you might as well use a relational database, since you won't be getting any benefit from the solution that has other ways to handle that.
It is late but will help many developers.
Verified with
"mongodb": "^3.6.2",
"mongoose": "^5.10.8",
Join two collections in mongoose
ProductModel.find({} , (err,records)=>{
if(records)
//reurn records
else
// throw new Error('xyz')
})
.populate('category','name') //select only category name joined collection
//.populate('category') // Select all detail
.skip(0).limit(20)
//.sort(createdAt : '-1')
.exec()
ProductModel Schema
const CustomSchema = new Schema({
category:{
type: Schema.ObjectId,
ref: 'Category'
},
...
}, {timestamps:true}, {collection: 'products'});
module.exports = model('Product',CustomSchema)
Category model schema
const CustomSchema = new Schema({
name: { type: String, required:true },
...
}, {collection: 'categories'});
module.exports = model('Category',CustomSchema)

Resources