How to do field selection on find() in the mongodb native driver? - node.js

I am using the mongodb native driver for node.js and can't get to work the field selection. What I want to do is to limit fields to name. I do not want the 'last' in the output.
I am doing this:
db.collection("test").find({},[{'name':true,'last':false}]).toArray(function(err, results) {
console.dir(results);
});
But the log prints:
[ { _id: 524b53588aa4f388de1c2ddb },
{ _id: 524b53548aa4f388de1c2dda } ]
So there is no name in the output.
Update:
I have tried an object instead of array -- did not work. The reason is really mixing inclusion and exclusion. You can't mix it. When I only had "name":true it worked.

If you are using latest mongodb 3.0 nodejs driver, then try this code:
db.collection('test').find({}).project({name: 1, last: 1}).toArray();

The field selection argument to find is an object, not an array. And you can't mix field inclusion and exclusion (except for _id), so it should be:
db.collection("test").find({}, {'name': true}).toArray(function(err, results) {
console.dir(results);
});

The recommended way of doing this in v3.0 is with the projection field in the options object:
db.collection('test').find({}, {projection: {name: 1}}).toArray()
As mentioned in the accepted answer, you still cannot mix inclusion and exclusion.
See: http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#find

Omit the array:
db.collection("test").find({},{'name':true,'last':false}).toArray(function(err, results) {
console.dir(results);
});

Related

Mongoose, Nodejs - replace many documents in one I/O?

I have an array of objects and I want to store them in a collection using only one I/O operation if it's possible. If any document already exists in the collection I want to replace it, or insert it otherwise.
These are the solutions that I found, but doesn't work exactly as I want:
insertMany(): this doesn't replace the document that already exists, but throws exception instead (This is what I found in the Mongodb documentation, but I don't know if it's the same as mongoose).
update() or ‎updateMany() with upsert = true: this doesn't help me as well, because here I have to do the same updates to all the to stored documents.
‎There is no replaceMany() in mongodb or mongoose.
Is there anyone how knows any optimal way to do replaceMany using mongoose and node.js
There is bulkWrite (https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/), which makes it possible to execute multiple operations at once. In your case, you can use it to perform multiple replaceOne operations with upsert. The code below shows how you can do it with Mongoose:
// Assuming *data* is an array of documents that you want to insert (or replace)
const bulkData = data.map(item => (
{
replaceOne: {
upsert: true,
filter: {
// Filter specification. You must provide a field that
// identifies *item*
},
replacement: item
}
}
));
db.bulkWrite(bulkData);
You need to query like this:
db.getCollection('hotspot').update({
/Your Condition/
}, {
$set: {
"New Key": "Value"
}
}, {
multi: true,
upsert: true
});
It fulfils your requirements..!!!

Mongoose Query: compare two values on same document

How can I query a Mongo collection using Mongoose to find all the documents that have a specific relation between two of their own properties?
For example, how can I query a characters collections to find all those characters that have their currentHitPoints value less than their maximumHitPoints value? Or all those projects that have their currentPledgedMoney less than their pledgeGoal?
I tried to something like this:
mongoose.model('Character')
.find({
player: _currentPlayer
})
.where('status.currentHitpoints').lt('status.maximumHitpoints')
.exec(callback)
but I am getting errors since the lt argument must be a Number. The same goes if I use $.status.maximumHitpoints (I was hoping Mongoose would be able to resolve it like it does when doing collection operations).
Is this something that can be done within a Query? I would expect so, but can't find out how. Otherwise I can filter the whole collection with underscore but I suspect that is going to have a negative impact on performance.
PS: I also tried using similar approaches with the find call, no dice.
MongoDB 3.6 and above supports aggregation expressions within the query language:
db.monthlyBudget.find( { $expr: { $gt: [ "$spent" , "$budget" ] } } )
https://docs.mongodb.com/manual/reference/operator/query/expr/
Thanks to Aniket's suggestion in the question's comments, I found that the same can be done with Mongoose using the following syntax:
mongoose.model('Character')
.find({
player: _currentPlayer
})
.$where('this.status.currentHitpoints < this.status.maximumHitpoints')
.exec(callback)
Notice the $where method is used instead of the where method.
EDIT: To expand on Derick's comment below, a more performance sensitive solution would be to have a boolean property inside your Mongoose schema containing the result of the comparison, and update it everytime the document is saved. This can be easily achieved through the use of Mongoose Schema Plugin, so you would have something like:
var CharacterSchema = new mongoose.Schema({
// ...
status: {
hitpoints: Number,
maxHitpoints: Number,
isInFullHealth: {type: Boolean, default: false}
}
})
.plugin(function(schema, options) {
schema.pre('save', function(next) {
this.status.isInFullHealth = (this.status.hitPoints >= this.status.maxHitpoints);
next();
})
})
mongoose.model('Character')
.find({
player: _currentPlayer, $expr: { $lt: ['$currentHitpoints', '$maximumHitpoints'] }
})
This above query means find the record which has currentHitpoints less than maximumHitpoints
Starting in MongoDB 5.0, the $eq, $lt, $lte, $gt, and $gte comparison operators placed in an $expr operator can use an index on the from collection referenced in a $lookup stage.
Example
The following operation uses $expr to find documents where the spent amount exceeds the budget:
db.monthlyBudget.find( { $expr: { $gt: [ "$spent" , "$budget" ] } } )

sorting alpha with mongoose

I'm trying to sort via mongoose 3.6.20 and I am receiving some unexpected results.
I have a list of companies with a name. At first I thought that maybe it was sorting in a case sensitive way. Which based on articles, I expect was true.
I'm now using a virtual property to down case the sort field. However, I'm still getting unexpected results.
CompanySchema.virtual('name_lower').get(function(){
return this.name.toLowerCase();
});
and when I sort
Company.find().sort({ name_lower: 1 });
I'm getting it in the following order:
company name
google
company name (yes a duplicate for testing)
I'm also outputting the value of my virtual property and it looks right. There is no whitespace or funky characters that would result in the 2nd 'company name' from appearing after google.
Using nodejs, express, mongoose.
What am I missing or doing incorrectly?
Update:
Based on the information provided in the answers, I refactored my schema to include some normalized fields and hooked into the pre save event of my document, where I update those normalized fields and sort using them in all future queries.
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
Next, is in the schema I use:
var CompanySchema = mongoose.Schema({
...
normalized_name: { type: String, set: normalize },
...
});
Where normalize is a function that for now, returns a lowercase version of the value passed into it. However, this allows me to expand on it later really fast, and I can quickly do the same to other fields that I might need to sort against.
As of MongoDB v3.4, case insensitive sorting can be done using collation method:
Company.find()
.collation({locale: "en" }) //or whatever collation you want
.sort({name:'asc'})
.exec(function(err, results) {
// use your case insensitive sorted results
});
Unfortunately MongoDB and Mongoose does not currently support complex sorting, so there are 2 options:
As you said, create a new field with the names sanitized to be all lowercase
Run a big for loop over all the data and update each company name to it's lower case form:
db.CompanyCollection.find().forEach(
function(e) {
e.CompanyName = e.CompanyName.toLowerCase();
db.CompanyCollection.save(e);
}
)
or
db.CompanyCollection.update({_id: e._id}, {$set: {CompanyName: e.CompanyName.toLowerCase()
Please see Update MongoDB collection using $toLower and Mongoose: Sort alphabetically as well for more info.
I want to put out that in this hook:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
You'll have to call next(); at the end, if you want the normalized_name to be saved in the database, so the pre save hook would look like:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
next();
});
This answer seems to be more helpful to me. I had to consider diacritics along with the case so I had used strength:3.
Mongoose: Sort alphabetically

Override document on update

I am doing a findOneAndUpdate in mongoose:
Item.findOneAndUpdate({_id: 12345}, updateDoc, function (err, updatedItem) {
//....
});
However I want to completely overwrite the document. According to mongoose docs:
All top level keys which are not atomic operation names are treated as
set operations:
Is there anyway that I can override that behavior such that mongoose does not issue a $set operation for top level elements and instead overwrite the document?
An "overwrite" option has recently been added. It replaces the entire document, the way Mongo updates by default. It's used like this:
Item.findOneAndUpdate({_id: 12345}, updateDoc, {overwrite: true}, function(err, updatedItem) {
....
});
I found some history on this feature in thier GitHub Issues area.

Mongoose: Using addToSet with ObjectIds Results in Orphan Id

I am having a rather interesting problem using mongoDB's $addToSet to an array full of ObjectIds.
In my mongoose schema ("Happening"), I declare an array of ObjecIds called "expected", to be used by .populate().
expected: [{type: Schema.Types.ObjectId, ref: "User" }]
... which works nicely everywhere I use it. So far so good.
I then attempt to update the Happening.expected array using $addToSet as outlined here:
http://docs.mongodb.org/manual/reference/operator/addToSet/
like so:
app.get("/happening/yamobethere/:id", ensureLoggedIn("/login"),
function (req, res) {
// userId is the mongo ObjectId of the user record
var userId = req.session.user.id,
eventId = req.params.id;
models.Happening.update(
{_id: eventId}, {
$addToSet: {expected: userId}
},
function(err, updated){
if (err) {
res.json({"error": err});
}
res.json({"updated": updated});
});
});
... which always yields:
{updated: 1}
Now the docs lead me to expect the actual userId that I passed in, so the "1" is a bit odd. I expected it to be a fail, and in light of the weirdness that happens next, it appears to be a mongodb error of some sort percolating it's way back to me as results.
The weirdness is, when I check my database, I see that indeed a new ObjectId has been added: just not the one I passed in.
"expected" : [
ObjectId("51cb18623ade2b9f1e000004"),
ObjectId("51cdb7c12f0e58bdb3000001")
],
becomes
"expected" : [
ObjectId("51cb18623ade2b9f1e000004"),
ObjectId("51cdb7c12f0e58bdb3000001"),
ObjectId("51cdb80e09612bfab3000002")
],
The new ObjectId does not appear in any of my collections. It appears to be an orphan, but I'm a mongo noob, so I may be full of compost on this.
I did attempt to cast the userId as an ObjectId:
$addToSet: {expected: mongoose.Types.ObjectId.fromString(userId)}
but that changed nothing, and really should not be necessary, since the schema should handle it.
I'd really rather not resort to downloading the entire object, appending the value to the "expected" array, then sending the whole schmear back for an update.
Any help appreciated, folks. Thanks!
Update:
A colleague suggested the following technique:
var addMe = {$addToSet: {expected: userId}};
models.Happening.findByIdAndUpdate(eventId, addMe, function(err, me) {
if (err) {
return json(err);
}
res.json(200, me);
});
... which is a bit of an improvement, since it actually returns an object for me to inspect. Unfortunately, it also results in orphaned ObjecIds appearing in the array, rather than the existing userId value I specified.
Thanks again!
It appears that my passport strategy is returning the ObjectID of the rejected attempted creation of a new user in the db via data from oauth. So, the code is fine, my data is garbage.
Never trust anything, and be prepared to look like a boob. :-)
Thanks for the clarification on my return values JohnnyHK.

Resources