How to execute a query with a `has many` association using MongoDB - javascript

I have a setup with Node, Express, and using backbone. Everything works fine and I'm able to retrieve records from by MongoDB collections when they are simple as in getting employees by id or all employees. What I'm having trouble understanding is how to get collections from MongoDB that require a more complex query syntax like the following:
db.employees.aggregate(
[
{ $group : { _id : "$managerName", employees: { $push: "$fullName" } } }
]
)
I currently have the following syntax for extracting the data I want to expose as a json object and bind to the elements in my html page.
exports.findById = function(req, res) {
var id = parseInt(req.params.id);
db.collection('employees', function(err, collection) {
collection.findOne({'id': id}, function(err, item) {
res.jsonp(item);
});
});
};
I want to get a list of all Managers and they employees that report to them and then somehow bind this result set to individual divs that would list a Manager as the List heading, and then all the employees that report to them as list items. The result set will essentially consist of parents and childs. I want to do this dynamically using backbonejs.
Would I be doing something like
exports.findRelations = function(req, res) {
db.collection('employees', function(err, collection) {
collection.aggregate({ $group : { _id : "$managerName", employees:{$push: "$fullName" } } }, function(err, item) {
res.jsonp(item);
});
});
};

The aggregation pipeline for MongoDB requires that you pass the operations in an Array. This means the correct query would be:
db.collection('employees').aggregate([
{ $group : { _id : "$managerName", employees:{$push: "$fullName" } }
])
.toArray(function(err, managers){
if (err){
throw err;
}
res.jsonp(managers);
});
You'll find details for using the aggregation pipeline with the NodeJS MongoDB driver here: https://docs.mongodb.org/getting-started/node/aggregation/

Related

Mongoose search by array entries ($in) behavior not aligned with MongoDB Atlas

I'm running a Node.js server, connecting to a MongoDB database with mongoose.
Inside my controller, I have several methods that make operations to the database. One of them is this one:
async findMultiple(req, res) {
const [baseSkillsArray] = Array(req.body);
try {
// if there is not baseSkillsArray, skip
if (!baseSkillsArray) {
return res.status(200).send([]);
}
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({
_id: { $in: [baseSkillsArray.baseSkillArray] } //
});
console.log('test ' + allBaseSkills);
res.status(200).send(allBaseSkills);
} catch (error) {
console.error(error.message);
res.status(500).send('Server error find BaseSkills');
}
}
However, this returns me nothing. I did some debugging and I found the reason is the find id $in the array. So I tried hard coding a value, like '2', for instance.
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({ _id: { $in: ['2'] } });
No success. So I went to MongoDB Atlas, where my DB is stored. I tried filtering using the same line of code in my collections.
{ _id: { $in: ['2'] } }
Surprisingly, it returns my document as I wanted!
The issue is that I need to make it work with mongoose. Any ideas? Is this a known bug?
There is nothing wrong with the query, nor a bug regarding $in.
In fact, what's wrong is the actual collection name. I manually created a collection in MongoDB Atlas, called "baseSkills". However, mongoose by default transforms your collection name into lowercase and adds an "s" if your collection's name is not in the plural.
So every time I started my server, I noticed that there was a new collection called "baseskills". I assumed it was a bug and deleted it. Only after making this post that I realized the collection was there again.
So I exported the documents to this collection and my query was working fine.
FYI, there is a way to enforce the collection's name in mongoose. When you declare you model, add a second parameter to the Schema function called "collection". Here is an example:
const BaseSkillSchema = new mongoose.Schema({
_id: {
type: String,
required: true
}, ...
}, { collection: 'baseSkills' })
That's it! Sorry for the mess and thank you for your help!
you want to query over mongo db object ids. So you should create a new ObjectId to do that.
import {Types} from 'mongoose';
{ _id: { $in: [new Types.Object("2")] } }
Or if you have 2 ids one generated and one custom created as id then you can query without creating a new object.
{ id: { $in: ['2'] } }

Meteor MongoDB Setting field of array per document

I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.

Mongo DB Schema arrange and get correct data from nested array in search

I am learning backend coding
I am using single collection to data save to db
Collection name is book , each book has few nested arrays (like category and may be sub categories )
I have to show all categories title of each books in client side in category search scenario.
book = [
{
name: '';
----------
----------
category:[
{
title: ''
}
----------
----------
]
}
]
How to find and get data using mongoose ?
Do I have to create separate collection for nested arrays ? is that right way?
// Get all category
exports.Allcategory = function(req, res){
Book.find({}, function(err, category){
if (err) return res.json({message: 'Error on the server!', status: 500 });
return res.json(category);
});
};
In your Backend code, you are sending whole document of Book, not just category array.
In your case this approach will be like this.
Books.map(item =>
{item.name} // this will print book name. in this iteration you can get any property defined at this level
{item.category.map(c => ){
{c.title} //this will print category name
}}
){
}

How to update MongoDB with no duplicates

I'm using Meteor framework with Blaze. How can I fetch data from an API and only insert new data in my MongoDB collection and not duplicates?
Fetching data from the API.
if (Meteor.isServer) {
Meteor.methods({
fetchApiData: function () {
this.unblock();
return Meteor.http.call('GET','http://jsonplaceholder.typicode.com/posts');},
Insert data into database:
populateDatabaseApi: function () {
Meteor.call('fetchApiData', function(error, result) {
myCollection.insert({
//upsert: true,
A: result.data.title,
B: result.data.userId,
C: result.data.id });
});
},
When using "myCollection.update" with "upsert: true" it does not insert new entries obviously. What is best practice to go about checking the API for data and inserting ONLY new entries with no duplicates and updating existing entries?
Thank you.
here's how i handle what i call reference data at startup. it's driven off of JSON data. you have to pick a field that serves as your "reference" for each JSON object, so you can see if it's already in the db.
_.each(ItemData.items, function(q) {
check(q, ItemsSchema);
Items.upsert({
item: q.item
}, {
$set: {
item: q.item,
}
}, function(error, result) {
if (error) {
let errMsg = 'Error while writing item data';
console.error(errMsg, error);
throw new Meteor.Error('500', errMsg);
}
});
});
i use an upsert to handle insert vs update.
I'm not familiar with your specific framework, so I can't help with syntax, but you should be able to find all documents with the same properties as the document you're trying to insert (there should be only one). If there is one, then save it using upsert. If there isn't, then the object you're saving is unique, and you should save a new one.
Using only "vanilla" Meteor, assuming your api object have unique ids and that you have the proper data access (ie, if item exist findOne would find it), I'd use :
populateDatabaseApi: function () {
Meteor.call('fetchApiData', function(error, result) {
var item = myCollection.findOne({A : result.data.id})
if(item){
//do nothing, this item already is in the db
}else{
myCollection.insert({
A: result.data.title,
B: result.data.userId,
C: result.data.id });
});
}
},

what is the mongoose equivalent for the SQL query

select _id from project where projectName = '***' order by viewCount desc limit 5
i'm still new to mongoose and have a somewhat intermediate SQL understanding, here's my attempt of it as i'm trying to retrieve the ObjectId of the sorted return
ProjectModel.find({id}).sort({viewCount: -1}).limit(5).exec(
function(err, projects) {
...
}
);
ProjectModel.find({projectName: '***'}, ["_id"]).sort({viewCount: -1}).limit(5).exec(
function(err, projects) {
...
}
);

Categories