I need to modify an existing geospatial query so that it includes the distance in the search results. (Both the document and the existing query are using legacy coordinate pairs.) The original query uses $near. Reading the MongoDB documentation it looks like geoNear should return distance, as is shown in several examples.
While I have been able to modify the query to use geoNear, the distances are not included in the search results. Here is an example of the new query:
{
geoNear: 'users',
near: [ '0', '0' ],
maxDistance: '90',
query: { userName: { '$regex': '^test' } }
}
One thing that's not clear to me is how Mongo ties the location to the specified in the query to the location of the document. In my case the users document has a field named lastKnownPosition. How does Mongo even know to query against that field?
Here's an example of the search results:
{
"__v" : 0 ,
"_id" : { "$oid" : "5413824f8b4d6f7505120a53"} ,
"lastKnownPosition" : { "lon" : 0 , "lat" : 0} ",
"userName" : "test123"
}
I cannot think of a single case where a distance would not be returned. So you must be doing something different to how this is represented in this sample:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var geoSchema = new Schema({
"lastKnownPosition": {
"lon": Number,
"lat": Number
},
"userName": String
});
geoSchema.index({ "lastKnownPosition": "2d" });
var Geo = mongoose.model( "Geo", geoSchema, "testgeo" );
mongoose.connection.on("open", function(err,conn) {
async.series(
[
function(callback) {
Geo.remove({},function(err) {
callback();
});
},
function(callback) {
Geo.create(
{
"lastKnownPosition": { "lon": 0, "lat": 0 },
"userName": "test123"
},
function(err,doc) {
if (err) throw err;
callback();
}
);
},
// Mongoose method
function(callback) {
Geo.geoNear(
[0,0],
{
maxDistance: 90,
query: { 'userName': { '$regex': '^test' } }
},
function(err,docs) {
if (err) throw err;
console.log( docs );
callback();
});
},
// Native method
function(callback) {
Geo.db.db.executeDbCommand(
{
"geoNear": "testgeo",
"near": [ 0, 0 ],
"maxDistance": 90,
"query": { 'userName': /^test/ },
},function(err,result) {
if ( err ) throw err;
console.log( result.documents[0].results[0] );
callback();
}
);
},
// aggregate method
function(callback) {
Geo.aggregate(
[
{ "$geoNear": {
"near": [0,0],
"distanceField": "distance",
"maxDistance": 90,
"query": { "userName": { "$regex": "^test" } }
}}
],
function(err,result) {
if (err) throw err;
console.log( result );
callback();
}
);
}
],
function(err) {
mongoose.disconnect();
}
);
});
Which produces output like the following:
[ { dis: 0,
obj:
{ userName: 'test123',
__v: 0,
_id: 54225696ce2837e4495cd188,
lastKnownPosition: { lon: 0, lat: 0 } } } ]
{ dis: 0,
obj:
{ _id: 54225696ce2837e4495cd188,
userName: 'test123',
lastKnownPosition: { lon: 0, lat: 0 },
__v: 0 } }
[ { _id: 54225696ce2837e4495cd188,
userName: 'test123',
lastKnownPosition: { lon: 0, lat: 0 },
__v: 0,
distance: 0 } ]
All have a "distance" field, which is defaulted to "dis" and separate to the document by what is the "geoNear" command in either invocation or is specified and included within the document from the aggregate $geoNear operator.
Follow any of those patterns and you will get the results you want.
You can only create one 2d index per collection, so mongo directly knows which field to query against.
http://docs.mongodb.org/v2.2/core/geospatial-indexes/#create-a-geospatial-index
Related
I have a product schema like
quantity: { type: Number, required: true },
batch_no: {
type: [
{
batch_no: { type: String, required: true },
quantity: { type: Number, required: true },
created_at: { type: Date, default: Date.now() },
}
],
default: []
}
I am trying to update both the quantity fields in one query.
The code goes something like :
var expression = { $and: [{ "$inc": { "quantity": -10 } }, { "$inc": {"batch_no.$.batch_no": -10 } }] }
await ProductModel.findOneAndUpdate({ "sku": "TestSKU", "batch_no.$.batch_no":"Batch 1" }, { expression }, (err, response) => {
if (response) {
console.log("Update success")
} else if (err) {
res.status(400).send(err);
}
});
This nested query does not work.
Is there no way that I can update both the quantites at once?
$and is a query operator. If you just wanted to update multiple fields via $inc, you can pass them as key-value pairs object argument to $inc, like this:
var expression = { $inc: { "quantity": -10, "batch_no.$.batch_no": -10 } }
await ProductModel.findOneAndUpdate({ "sku": "TestSKU", "batch_no.$.batch_no": "Batch 1" }, expression, (err, response) => {
if (response) {
console.log("Update success")
} else if (err) {
res.status(400).send(err);
}
});
Also, you can just pass in expression directly as the 2nd argument, without wrapping it in another object.
I am trying to insert a time object into the times array for a specific activity name for a specific user. For example, if the user was "someuser" and I wanted to add a time to the times for guitar I am unsure as to what to do.
{
username: "someuser",
activities: [
{
name: "guitar",
times: []
},
{
name: "code",
times: []
}
]
}, {
username: "anotheruser",
activities: []
}
This is currently the function that I have, I cannot figure out what I am doing wrong, any help would be greatly appreciated:
function appendActivityTime(user, activityName, newRange) {
User.updateOne(
{username: user, 'activities.name': activityName},
{ $push: {'activities.$.times': {newRange}},
function (err) {
if (err) {
console.log(err);
} else {
console.log("Successfully added time range: " + newRange);
}
}}
);
}
appendActivityTime("someuser", "guitar", rangeObject);
i've tried your attempt and it worked for me:
db.getCollection("test").updateOne(
{ username: "someuser", "activities.name": "guitar" },
{ $push: { "activities.$.times": { from: ISODate(), to: ISODate() } } } //Don't worry about ISODate() in node.js use date objects
)
results:
{
"_id" : ObjectId("5f6c384af49dcd4019982b2c"),
"username" : "someuser",
"activities" : [
{
"name" : "guitar",
"times" : [
{
"from" : ISODate("2020-09-24T06:15:03.578+0000"),
"to" : ISODate("2020-09-24T06:15:03.578+0000")
}
]
},
{
"name" : "code",
"times" : [
]
}
]
}
what i would suggest you using instead is arrayFilter, they are much more precise and when you get used to them, they became very handy
If you are not confident with updating nested documents, let mongoose make the query.
let document = await Model.findOne ({ });
document.activities = new_object;
await document.save();
I know this has been asked before but I can't seem to find the answer, how to access data in data event, I want to show data for the next date in the collection JadwalBooking.
Schema:
"keterangan" : "keterangan di rubah",
"dataevent" : {
"time_start" : 60,
"time_end" : 660,
"_id" : ObjectId("5b3da607acddef1c24317dd0"),
"name" : "event 1",
"description" : "lorem ipsum, lorem ipsum",
"date" : ISODate("2018-11-25T00:00:00.000Z")
}
Query:
const data = await JadwalBooking.aggregate([
{
$match: {
dataevent: {
$elemMatch: {
date: {
$gte: new Date(new moment().format("YYYY-MM-DD")),
}
}
}
}
},
{
$project:
{
_id: 1,
dataevent: 1,
keterangan: 1,
}
},
{
$sort: { date: 1 }
}
]);
You need to use dot notation for query and sort in datevent date:
const data = await JadwalBooking.aggregate([
{
$match: {
"dataevent.date": {
$gte: new Date(new moment().format("YYYY-MM-DD"))
}
}
},
{
$project:
{
_id: 1,
dataevent: 1,
keterangan: 1,
}
},
{
$sort: { "dataevent.date": 1 }
}
]);
You dont need to use $elemMatch for your case, $elemMatch is used, when you want to query a specific Object from an array of Objects, and return only matched Object from the array.
In your case a simple query with "." notation will work.
Try this:
const data = await JadwalBooking.aggregate([
{
$match: {
dataevent.date: {
$gte: new Date(new moment().format("YYYY-MM-DD"))
}
}
},
{
$project:
{
_id: 1,
dataevent: 1,
keterangan: 1,
}
},
{
$sort: { date: 1 }
}
]);
As not mentioned specifically to the aggregation,
db.collection
.find({"dataevent.date" : {$gt : new Date(new moment().format("YYYY-MM-DD"))}})
.sort({"dataevent.date": 1})
One more thing is:
Based on your schema you really don't need to use $project too. As you are retrieving whole data.
Note:- $elemMatch is used for Arrays, you need to use dot notation.
const data = await JadwalBooking.aggregate([
{
$match: {
"dataevent.date": {
$gte: new Date(new moment().format("YYYY-MM-DD"))
}
}
},
{
$sort: { date: 1 }
}
]);
In my novice skill to Mongodb and Mongoose, I seem to be failing miserably at this fundamental task.
I have a bounded array of 10 elements. A user can only have 10 pets, so I figured to make it bounded with set fields and empty values was the best way.
The pets array values are blank at the time of creation, because the user can add pets as they go along. When I look in mongo console, the pets array is unbounded with no fields. I also can't add values to the array.
Mongoose Schema:
var userSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
username: { type: String, required: true, unique: true },
location: String,
created_at: Date,
pets: [
{ "pet0": {} },
{ "pet1": {} },
{ "pet2": {} },
{ "pet3": {} },
{ "pet4": {} },
{ "pet5": {} },
{ "pet6": {} },
{ "pet7": {} },
{ "pet8": {} },
{ "pet9": {} }
]
});
mongodb:
{ "_id" : ObjectId("56a3e324bdebcf801c1ca224"), "firstName" : "bob", "lastName" : "smith", "username" : "bob123", "pets" : [ ], "__v" : 0 }
When modifying the array:
UserModel.findOne({ firstName: "bob" }, 'pets', function(err, user) {
user.pets[0] = { "name": "felix", "type": "cat" }
user.save(function(err) { console.log(err); console.log('saved')});
});
output:
Mongoose: users.findOne({ firstName: 'bob' }) { fields: { pets: 1 } }
null
/home/one/github/foo/node_modules/mongoose/lib/schema/documentarray.js:100
doc.validate({ __noPromise: true }, function(err) {
^
TypeError: undefined is not a function
at /home/one/github/foo/node_modules/mongoose/lib/schema/documentarray.js:100:11
at DocumentArray.SchemaType.doValidate (/home/one/github/foo/node_modules/mongoose/lib/schematype.js:654:22)
at DocumentArray.doValidate (/home/one/github/foo/node_modules/mongoose/lib/schema/documentarray.js:78:35)
at /home/one/github/foo/node_modules/mongoose/lib/document.js:1156:9
at process._tickCallback (node.js:355:11)
MongoDB allows you to limit the number of elements in an array. This feature has also been implemented in Mongoose as part of an .update query. The steps for adding an element to an array and limiting its size are as follows:
Push the element(s) into the array.
Slice the array.
This snippet of code explains how to do this using Mongoose:
UserModel.findOne({ firstName: "bob" }, function(err, user) {
UserModel.update(user, {
$push: {
pets: {
$each: [{ name: "felix", type: "cat" }],
$slice: -10
}
}
}, function(err, numAffected) {
if (err) console.log(err);
console.log('updated');
});
});
I'm trying to get a sub-document in my User collection using mongoose. I followed the Mongoose Sub Document on its official website. It's written that:
Each document has an _id. DocumentArrays have a special id method for looking up a document by its _id.
var doc = parent.children.id(id);
Here is my code:
exports.editAccount = function(req, res) {
var user = new User(req.user);
var newAccount = new Account(req.body);
console.log("Account:" + newAccount._id); // Gave me 53bf93d518254f880c000009
var account = user.accounts.id(newAccount._id);
console.log("Account" + account); // Never been printed
};
The console.log("Account" + account); has never been printed. I don't know what happen. I tried many different ways, however, still can't figure it out. Any help would be appreciated.
User collection:
{
"__v" : 1,
"_id" : ObjectId("53bcf3e6fbf5adf10c000001"),
"accounts" : [
{
"accountId" : "123456789",
"type" : "Saving account",
"balance" : 100,
"_id" : ObjectId("53bf93d518254f880c000009")
}
]
}
I
Not too sure how you have defined your Schema or basically even model instances, but really all you need is this:
var accountSchema = new Schema({
"accountId": String,
"type": { "type": String, "enum": ["Saving Account", "Checking Account"] },
"balance": { "type": Number, "default": 0 }
]);
var userSchema = new Schema({
"accounts": [accountSchema]
]);
var User = mongoose.model( "User", userSchema );
Then when you want to add an account to the User you just do, presuming you have input that matches the first variable declaration:
var input = {
"accountId": "123456789",
"type": "Savings Account",
};
User.findByIdAndUpdate(
userId,
{ "$push": { "accounts": input } },
function(err,user) {
// work with result in here
}
);
That does bypass things like validation and other hooks, but is more efficient in communicating with MongoDB.
If you really need the validation and/or other features then you and using a .find() variant and issuing a .save() method.
User.findById(userId,function(err,user) {
if (err) throw err; // or handle better
user.accounts.push( input );
user.save(function(err, user) {
// more handling
});
]);
And to modify the document then you are doing much the same. Either by the most efficient MongoDB way:
var input = {
accountId: "123456789",
amount: 100
};
User.findOneAndUpdate(
{ "_id": userId, "accounts.accountId": input.accountId },
{ "$inc": { "accounts.$.balance": input.amount } },
function(err,user) {
// handle result
}
);
Or again where you need the Mongoose hooks and or validation to apply:
User.findById(userId,function(err,user) {
if (err) throw err; // or handle otherwise
user.accounts.forEach(function(account) {
if ( account.accountId === input.accountId )
account.balance += input.balance;
});
user.save(function(err,user) {
// handle things
});
);
Remember that these things are "arrays", and you can either handle them the MongoDB way or the JavaScript way. It just depends on where you choose to "validate" your input.
More code to illustrate where the usage is not correct:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/child');
var accountSchema = new Schema({
"accountId": String,
"type": { "type": String },
"balance": { "type": Number, "default": 0 }
});
var userSchema = new Schema({
"accounts": [accountSchema]
});
var User = mongoose.model( "User", userSchema );
async.waterfall([
function(callback) {
User.create({},function(err,user) {
if (err) throw err;
console.log(
"Created:\n%s\n",
JSON.stringify( user, undefined, 4 )
);
callback(null,user);
});
},
function(user,callback) {
var account = user.accounts.create({
"accountId": "123456789",
"type": "Savings"
});
console.log(
"Account is:\n%s\n",
JSON.stringify( account, undefined, 4 )
);
console.log(
"User is still:\n%s\n",
JSON.stringify( user, undefined, 4 )
);
user.accounts.push( account );
console.log(
"User Changed:\n%s\n",
JSON.stringify( user, undefined, 4 )
);
User.findById(user.id,function(err,saved) {
if (err) throw err;
console.log(
"Persisted is still:\n%s\n",
saved
);
user.save(function(err,user) {
if (err) throw err;
callback(null,user,account);
});
});
},
function(user,account,callback) {
User.findById(user.id,function(err,saved) {
if (err) throw err;
console.log(
"Persisted is now:\n%s\n",
saved
);
var item = user.accounts.id(account.id);
console.log(
"Item is:\n%s\n",
item
);
callback();
});
}
],function(err) {
process.exit();
});
Results:
Created:
{
"__v": 0,
"_id": "53c08ab51083d1fe3852becc",
"accounts": []
}
Account is:
{
"accountId": "123456789",
"type": "Savings",
"_id": "53c08ab51083d1fe3852becd",
"balance": 0
}
User is still:
{
"__v": 0,
"_id": "53c08ab51083d1fe3852becc",
"accounts": []
}
User Changed:
{
"__v": 0,
"_id": "53c08ab51083d1fe3852becc",
"accounts": [
{
"accountId": "123456789",
"type": "Savings",
"_id": "53c08ab51083d1fe3852becd",
"balance": 0
}
]
}
Persisted is still:
{ _id: 53c08ab51083d1fe3852becc, __v: 0, accounts: [] }
Persisted is now:
{ _id: 53c08ab51083d1fe3852becc,
__v: 1,
accounts:
[ { accountId: '123456789',
type: 'Savings',
_id: 53c08ab51083d1fe3852becd,
balance: 0 } ] }
Item is:
{ accountId: '123456789',
type: 'Savings',
_id: 53c08ab51083d1fe3852becd,
balance: 0 }