Nodejs: Create search query with reference collection field - node.js

My User collection model schema:
var userModel = new Schema({
userAddress: { type: Object, ref: 'useraddress' },
name: String,
});
My User addresses collection model schema:
var addressModel = new Schema({
macAddress: String,
repeat: Number,
});
Get data method is:
module.exports.get = function (req, res) {
var _repeatTime = 2;
var _searchQRY = [];
_searchQRY.push(
{
"useraddress.repeat": { $gte: _repeatTime}
});
userModel.find({ $and: _searchQRY }).populate('useraddress').exec(function (err, results) {
res.json({ record: results})
});
This is my code. I want to filter with address repeat number. But i am not getting correct result with this query.

First Mongoose performs the the search on users collection by {"useraddress.repeat": {$gte: val}} query. And only after the call starts population.
So you should get 0 results as address is not yet populated.
Here are 2 ways of solving this. First, check out this answer please.
You'll need to:
//Any conditions that apply to not populated user collection documents
var userQuery = {};
userModel.find(userQuery)
//Populate only if the condition is fulfilled
.populate('useraddress', null, {"useraddress.repeat": { $gte: _repeatTime}})
.exec(function (err, results) {
results = results.filter(function(doc){
//If not populated it will be null, so we filter them out
return !!doc.useraddress;
});
//Do needed stuff here.
});
The second way is to use aggregation and $lookup (you'll need mongodb v 3.2+). Basically it means to move this population and filtering to DB level.
userModel
.aggregate()
//Anything applying to users collection before population
.match(userQuery)
.lookup({
from: 'address', //Please check collection name here
localField: 'useraddress',
foreignField: '_id',
as: 'useraddress'
})
//Lookup pushes the mathes to an array, in our case it's 1:1, so we can unwind
.unwind('useraddress')
//Filter them as you want
.match({'useraddress.repeat': { $gte: _repeatTime}})
.exec(function (result) {
//Get the result here.
});

Related

How to pass array instead of required string NodeJS

Script logic - script receives data from Binance API > Then I have aggregation $avg to calculate the average of one asset. I will have more than one collection so I need to calculate average of every asset.
I have an array where I store collection names for MongoDB.
const symbols = ["ADABTC", "AEBTC", "AIONBTC"]
And I want to calculate average from MongoDB collection.
const collection = db.collection(symbols);
Here - symbols doesn't work for me, but if I simply add "ADABTC" then it works, but it doesn't fixes my problem since I want to use different collection names one after another one.
How I can pass an array if it's required to be a string? I need to use more than 1 collection names.
FULL CODE
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
// Connection URL
const url = 'mongodb://username:password#serveripadress:port/dbname?retryWrites=true&w=majority';
const symbols = ["ADABTC", "AEBTC", "AIONBTC"]
// Database Name
const dbName = 'Crypto';
// Create a new MongoClient
const client = new MongoClient(url, { useUnifiedTopology: true });
// Use connect method to connect to the Server
client.connect(function(err, client) {
assert.equal(null, err);
console.log("Connected correctly to server");
const db = client.db(dbName);
simplePipeline(db, function() {
client.close();
});
});
function simplePipeline(db, callback) {
const collection = db.collection(symbols);
collection.aggregate(
[{
'$group': {
_id: null,
'Volume': {
'$avg': '$Volume'
}
}
}],
function(err, cursor) {
assert.equal(err, null);
cursor.toArray(function(err, documents) {
console.log(documents)
callback(documents);
});
}
);
}
It is not possible to pass an array into a function that is asking for a string. In your case what you need to do is join three collections. If you need to aggregate across multiple collections you can use the $lookup aggregation pipeline operator. You can connect using the first collection:
db.collection(symbols) => db.collection(symbols[0])
Then modify your query to join the three collections:
// Join with AEBTC collection
{
$lookup:{
from: symbols[1],
localField: //name of a value in first collection
foreignField: //name of same value in second collection
as: //what you want to call it in the second table
}
},
{ $unwind: //what you called it },
// Join with AIONBTC collection
{
$lookup:{
from: symbols[2],
localField: //name of value in joined collections 1 and 2
foreignField: //name of that value in collection 3,
as: //whatever you want to call it in the joined collection
}
},
{ $unwind: //what you called it },
// define some conditions here
{
$match {}
},
// define which fields are you want to fetch
{
$group: {
_id: null,
'Volume': {
'$avg': '$Volume'
}
}

Store value of a subquery - mongoose

What im doing:
When I call getData() the backend server .find() all my data.
My documents:
My test document has an _id a name and stuff fields. The stuff field contains the _id to the data document.
My data document has an _id and a age field
My goal:
When I send the data to the frontend I donĀ“t want the stuff field to appear with the _id, I want it to appear with the age field from the correspondingdata.
What I have:
router.route('/data').get((req, res) => {
Test.find((err, aval) => {
if (err)
console.log(err);
else{
var result = [];
aval.forEach(e => {
var age;
// Get the age, only 1
Data.findById(e.stuff, 'age', function (err, a) {
age = a.age;
});
result.push({name: e.name, age: age});
});
res.json(result);
}
});
});
I find all the test documents then, for each one of them, I find the age and put the result in the array. Finaly I send the result array.
My problem:
The age field on my result array is always undefined, why? Any solutions?
UPDATE 1 - The schemas
The test schema
var TestSchema = new Schema(
{
stuff: {type: Schema.Types.ObjectId, ref: 'Data', required: true},
name: {type: String, required: true}
}
);
The data schema
var DataSchema = new Schema(
{
age: {type: Number, required: true}
}
);
router.route('/data').get((req, res) => {
Test.find({})
.populate('stuff')
.exec((err, aval) => {
if (err) console.log(err);
res.json(aval);
});
});
Mongoose model has a populate property that uses the value in the model attribute definition to get data matching the _id from another model.
It's a scop problem with your code try this out :
Data.findById(e.stuff, 'age', function (err, a) {
result.push({name: e.name, age: a.age});
});
But as a better solution think to use the Aggregation Framework

Get the _id of the sub-document from mongoose findOne query

The schema of my Sample model is:-
var nameSchema = new mongoose.Schema({
firstname:String,
lastname:String
})
var sampleSchema= new mongoose.Schema({
number: {
type: String
},
name :{
type : [nameSchema]
}
});
I am trying to update the first and last name by searching them by their number property by making use of Sample.findOne({number:number}). And i am performing the update operation in the following manner:-
module.exports.updateNumber = function(req, res){
var number= req.body.number;
var lname= req.body.lname;
var fname= req.body.fname;
Sample
.findOne({number:number})
.select('name')
.exec(function(err, doc){
console.log(doc)
var this_id;
var thisService = doc.name.id('this_id');
thisService.firstname=fname;
thisService.lastname=lname;
doc.save(function(err, update) {
if (err) {
res
.status(500)
.json(err);
} else {
res
res.render('done')
}
});
})
}
If i console log the output i got is:
{ _id: 5bc5d71f47ff14361c0639d1,
name:
[ { _id: 5bc5d71f47ff14361c0639d2,
firstname: 'firstname',
lastname: 'lastname' } ] }
Is there any way, i could store _id: 5bc5d71f47ff14361c0639d2 in 'this_id' variable, so that this updation would be possible
name is an array, so if you want the first _id then name[0]._id would suffice, if you want an array of all values for _id in name, then name.map((person) => person._id) would give you an array of _id
However, more details about the context of this object would help give a better answer.

nodejs app mongoose database where clause with join

I have a schema article defined as:
var ArticleSchema = new Schema({
title: String,
content: String,
creator: {
type: Schema.ObjectId,
ref: 'User'
}
})
And user schema:
var UserSchema = new Schema({
type: String, //editor, admin, normal
username: String,
password: String,
})
I need to query all the article created by editor, i.e. in sql language
select Article.title as title, Article.content as content
from Article inner join User
on Article.creator = User._id
where User.type = 'editor'
This is what I have tried
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'creator.type': creatorType}
Article.find(criteria).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
}
The returned articles is an empty array []
I also tried Article.populate('creator').find(criteria), also not working with error:
utils.populate: invalid path. Expected string. Got typeof `undefined`
There is no concept of joins in MongoDB, as it is a not a relational database.
The populate method is actually a feature of Mongoose and internally uses multiple queries to replace the referred field.
This will have to be done using a multi-part query, first on the User collection, then on the Article collection.
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'type': creatorType}
User.distinct('_id', criteria, function (err, userIds) {
if (err) return next(err);
Article.find({creator: {$in: userIds}}).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
})
}

How to Make Mongoose's .populate() work with an embedded schema/subdocument?

I read up that you can make Mongoose auto pouplate ObjectId fields. However I am having trouble structuring a query to populate fields in a subdoc.
My models:
var QuestionSchema = new Schema({
question_text: String,
type: String,
comment_field: Boolean,
core_question: Boolean,
identifier: String
});
var SurveyQuestionSchema = new Schema({
question_number: Number,
question: {type: Schema.Types.ObjectId, ref: 'Question', required: true} //want this popuplated
});
var SurveySchema = new Schema({
start_date: Date,
end_date: Date,
title: String,
survey_questions: [SurveyQuestionSchema]
});
Right now I achieve the effect by doing:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var len = data.survey_questions.length;
var counter = 0;
var data = data.toJSON();
_.each(data.survey_questions, function(sq){
Question.findById(sq.question, function(err, q){
sq.question = q;
if(++counter == len) {
res.send(data);
}
});
});
});
Which obviously is a very error-prone way of doing it...
As I noted in the comments above, this is an issue currently under scrutiny by the mongoose team (not yet implemented).
Also, looking at your problem from an outsider's perpsective, my first thought would be to change the schema to eliminate SurveyQuestion, as it has a very relational db "join" model feel. Mongoose embedded collections have a static sort order, eliminating the need for keeping a positional field, and if you could handle question options on the Survey itself, it would reduce the schema complexity so you wouldn't need to do the double-populate.
That being said, you could probably reduce the queries down to 2, by querying for all the questions at once, something like:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var data = data.toJSON();
var ids = _.pluck(data.survey_questions, 'question');
Question.find({_id: { $in: ids } }, function(err, questions) {
_.each(data.survey_questions, function(sq) {
sq.question = _.find(questions, function(q) {
var id = q._id.toString();
return id == sq.question;
});
});
res.send(data);
});
});

Resources