find mongoose using model with hierarchy - node.js

I'm looking for the mongodb database to find object with value lat > 0 (latitude of user). But the result is empty.
What is the correct query? In my case {pos:{lat:{ $gte : 0 }}} is incorrect.
the code:
var userSchema = new mongoose.Schema({
name: String,
activated:{ type:Number, min:0, max:1 },
pos:{
lat:{ type:Number, min:-90. , max: 90.},
lon:{ type:Number, min:-180., max:180.}
}
});
User.find({pos:{lat:{ $gte : 0 }}},function(err, vals) {
if (err) return console.error(err);
console.dir(vals.length);//shows zero length
});
But if I'm trying to find
User.find({ activated:1 },function(err, vals) {
if (err) return console.error(err);
console.dir(vals.length);//shows correct size
});

Try to use
User.find({"pos.lat":{ $gte : 0 }}, function...

Related

Query complains about missing 2dsphere-index, but it's there

When I execute the following code (a larger example, boiled down to the essentials)
var mongoose = require("mongoose");
var LocationSchema = new mongoose.Schema({
userName: String,
loc: {
'type': { type: String, enum: "Point", default: "Point" },
coordinates: { type: [Number] }
}
})
LocationSchema.index({ category: 1, loc: "2dsphere" });
var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://user1:test#ds042417.mlab.com:42417/locationdemo';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });
var testUser = Location({
userName: "Tester",
loc: { coordinates: [12.44, 55.69] }
});
testUser.save(function (err) {
if (err) {
return console.log("UPPPPs: " + err);
}
console.log("User Saved, Try to find him:");
let query = Location.find({
loc:
{
$near:
{
$geometry:
{
type: "Point",
coordinates: [12.50, 55.71]
},
$maxDistance: 600000
}
}
})
query.exec(function (err, docs) {
if (err) {
return console.log("Err: " + err);
}
console.log("Found: " + JSON.stringify(docs))
})
});
I get this error:
Err: MongoError: error processing query: ns=locationdemo.locationsTree: GEONEAR field=loc maxdist=600000 isNearSphere=0
Sort: {}
Proj: {}
planner returned error: unable to find index for $geoNear query
But the index is there (see line 10) and the screenshot from mlab below. What am I doing wrong?:
You are breaking a rule of how you can use a an index in general. Whilst it is true that there is no restriction that a "2dsphere" index be the "first" property in a compound index, it is however very important that your "queries" actually address the first property in order for the index to be selected.
This is covered in Prefixes from the manual on compound indexes. In excerpt:
{ "item": 1, "location": 1, "stock": 1 }
The index has the following index prefixes:
{ item: 1 }
{ item: 1, location: 1 }
For a compound index, MongoDB can use the index to support queries on the index prefixes. As such, MongoDB can use the index for queries on the following fields:
the item field,
the item field and the location field,
the item field and the location field and the stock field.
However, MongoDB cannot use the index to support queries that include the following fields since without the item field, none of the listed fields correspond to a prefix index:
the location field,
the stock field, or
the location and stock fields.
Because your query references "loc" first and does not include "category", the index does not get selected and MongoDB returns the error.
So in order to use the index you have defined, you need to actually query "category" as well. Amending your listing:
var mongoose = require("mongoose");
mongoose.set('debug',true);
var LocationSchema = new mongoose.Schema({
userName: String,
category: Number,
loc: {
'type': { type: String, enum: "Point", default: "Point" },
coordinates: { type: [Number] }
}
})
//LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });
LocationSchema.index({ category: 1, loc: "2dsphere" });
var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });
var testUser = Location({
userName: "Tester",
category: 1,
loc: { coordinates: [12.44, 55.69] }
});
testUser.save(function (err) {
if (err) {
return console.log("UPPPPs: " + err);
}
console.log("User Saved, Try to find him:");
let query = Location.find({
category: 1,
loc:
{
$near:
{
$geometry:
{
type: "Point",
coordinates: [12.50, 55.71]
},
$maxDistance: 600000
}
}
})
query.exec(function (err, docs) {
if (err) {
return console.log("Err: " + err);
}
console.log("Found: " + JSON.stringify(docs))
})
});
As long as we include "category" everything is fine:
User Saved, Try to find him:
Mongoose: locations.find({ loc: { '$near': { '$geometry': { type: 'Point', coordinates: [ 12.5, 55.71 ] }, '$maxDistance': 600000 } }, category: 1 }, { fields: {} })
Found: [{"_id":"59f8f87554900a4e555d4e22","userName":"Tester","category":1,"__v":0,"loc":{"coordinates":[12.44,55.69],"type":"Point"}},{"_id":"59f8fabf50fcf54fc3dd01f6","userName":"Tester","category":1,"__v":0,"loc":{"coordinates":[12.44,55.69],"type":"Point"}}]
The alternate case is to simply "prefix" the index with the location. Making sure to drop previous indexes or the collection first:
LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });
As well as you probably should be in the habit of setting "background": true, else you start running into race conditions on unit tests where the index has not finished being created before unit test code attempts to use it.
My first solution to this problem was to create the index via the mLab web-interface which worked like a charm.
I have tried the solution suggested by Neil, but that still fails. The detailed instructions related to indexes, given by Neil however, did point me toward the solution to the problem.
It was a timing problem (which you not always see if you run the database locally) related to that my test code did the following:
Created the index, created a Location document (which first time will also create the collection), and then in the callback provided by save, I tried to find the user. It seems that the index was not yet created here, which is what gave the error.
If I delay the find method a second, using setTimeout it works fine.
But still, thanks to Neil for valuable information about the right way of using indexes (background) :-)

MongoDB aggregate, geNear and iterate over callback

I have a problem and I can´t find a solution. I have some MongoSchemas where I store Geolocation from users. Mobile Phone is sending me longitude and latitude every 5 minutes. This API is working perfectly.
Mongo-Schema looks like:
// Importing Node packages required for schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//= ===============================
// User Schema
//= ===============================
const GeolocationSchema = new Schema({
loc: {
type: { type: String },
coordinates: { type: [Number], index: '2dsphere' }
},
user: { type: Schema.Types.ObjectId, ref: 'User' }
},
{
timestamps: true
});
module.exports = mongoose.model('Geolocation', GeolocationSchema);
Now, I want to calculate users-nearby which have an "updateAt"-timestamp not even longer than 5 minutes in the past. That means that one or more users can be in a distance of e.g. 500m until 5 minutes in the past. This should be a match. For this I use Mongo aggregate, and I want to iterate the callback-result and extract the user._id out of the result to build a match.
This is what I tried:
const Geolocation = require('../models/geolocation')
User = require('../models/user'),
config = require('../config/main');
exports.setGeolocation = function (req, res, next) {
// Only return one message from each conversation to display as snippet
console.log(req.user._id);
var geoUpdate = Geolocation.findOneAndUpdate( { user: req.user._id },
{ loc: {
type: 'Point',
coordinates: req.body.coordinates.split(',').map(Number)
},
user: req.user._id
},
{upsert: true, new: true, runValidators: true}, // options
function (err, doc) { // callback
if (err) {
console.log(err);
}
});
// create dates for aggregate query
var toDate = new Date( (new Date()).getTime());
var fromDate = new Date( (new Date()).getTime() - 5000 * 60 );
var match = Geolocation.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: req.body.coordinates.split(',').map(Number)
},
distanceField: "dist.calculated",
maxDistance: 500,
includeLocs: "dist.location",
uniqueDocs: true,
query: { user: {$ne: req.user._id } , updatedAt: { $gte: fromDate,$lte: toDate }},
num: 5,
spherical: true
}
}], function (err, doc){
//here I´m going in trouble correctly parsing doc
var str = JSON.stringify(doc);
var newString = str.substring(1, str.length-1);
var response = JSON.parse(newString);
console.log(response.user);
});
res.sendStatus(200);
};
As you can see I´m going in trouble in parsing the "doc"-callback to iterate over the documents. If I want to parse it as jSON I´m getting an token-error on position 1. If I have more than 2 results, I´m getting an error on position 288.
That´s why I tried to parse and stringify the "doc". But this is not working correctly.
Maybe, someone could help me with a solution. I´m not familiar with mongo-functions because I´m starting with it, maybe there is a better solution but I can´t find something else to calculate geoNear and iterate afterwards over the results.
Thx at all who can help...

I get all all docs with populate mongoose

I am trying to make a populate query with mongodb (using mongoose as orm), and it doesn´t work 100%. I mean that I get all the entries from the document where I apply the populate... ALL, the entries that (on the crossed document) match with the match query (and I obtain a nested object), but I get the others too (and the nested object is null).
This is what I have done:
MODELS
var userSchema = Schema({
name: String,
email: String,
idiom: String
});
var ticketsSchema = Shema({
user_id:{ type:Schema.Types.ObjectId, ref:'User', required: true},
date: Date,
points: Number,
kart: String
});
POPULATE
tickets.find()
.populate(
{
path: 'user_id',
match:{name:"user1"}
}
).exec(function (err, result) {
if (err) return handleError(err);
res.send(result);
});
And a possible result could be:
Object[0]
__v: 0
_id: "5655b68ccbe953bc2a78da54"
date: "2015-11-25T13:24:28.561Z"
date: "2015-11-25T13:24:28.561Z"
points: 50
kart: "Senior"
user_id: Object
__v: 0
_company: 0
_id: "5655b656cbe953bc2a78da53"
name: "user1"
email: "user1#mail.com"
idiom: "es"
Object[1]
__v: 0
_id: "5655b732e0685c441fddb99b"
date: "2015-11-25T13:27:14.608Z"
points: 75
kart: "Pro Senior"
user_id: Object
__v: 0
_company: 0
_id: "5655b656cbe953bc2a78da53"
name: "user1"
email: "user1#mail.com"
idiom: "es"
Object[2]
__v: 0
_id: "56564613701da2981aa017d6"
date: "2015-11-25T23:36:51.774Z"
points: 75
kart: "Pro Senior"
user_id: null
Object[3]
__v: 0
_id: "565646ee701da2981aa017d8"
date: "2015-11-25T23:40:30.308Z"
points: 75
kart: "Pro Senior"
user_id: null
This isn´t exactly what I want to do... I would want that the two last doesn´t show up.
EDIT
I think I didn´t explain clear myself clearly... what I want to do is a JOIN query...
I have seen that I need use a mapreduce, I tried to do but I don´t found a place where it is explained for... dummies. All what I could do until now is this:
ticketsDB.find()
.populate(
{
path: 'user_id',
match:req.body
}
).exec(function (err, result) {
if (err) return res.send(err);
reslt=[];
for(var i in result)
if(result[i].user_id)reslt.push(result[i]);
console.log(reslt);
res.send(reslt);
});
Thank you.
Thanks #JohnnyHK by his comment, I found the solution here: Mongoose nested query on Model by field of its referenced model
And now the code is something like:
usersDB.find(req.body, function(err, docs) {
var ids = docs.map(function(doc) { return doc._id; });
ticketsDB.find({user_id: {$in: ids}})
.populate('user_id').exec(function (err, result) {
if (err) return res.send(err);
reslt=[];
for(var i in result)
if(result[i].user_id)reslt.push(result[i]);
res.send(reslt);
});
});
Where req.body is {name:"user 1"} or {email:"user1#user.com"}.
EDIT
I was thinking a little (sorry, it will happen no more ;p ), and I think that there is a way to remove the mapping of this query. Now my answer is this:
usersDB.find(req.body).distinct("_id", function(err, docs) {
ticketsDB.find({user_id: {$in: docs}})
.populate('user_id').exec(function (err, result) {
if (err) return res.send(err);
reslt=[];
for(var i in result)
if(result[i].user_id)reslt.push(result[i]);
res.send(reslt);
});
});
Could be that valid??

Creating a array element in mongoose

This is my schema.
team_year: String,
team: [{
_leader: String,
_member:[{
_name: String,
_phone: String,
_age: Number,
_gender: String,
_comment:[{
_date: String,
_contents: String,
_attendance: Boolean
}]
}]
}]
I have data
{ team_year: 2015
team: [
{
_leader: tom
_member: [
{_name: mike,
_phone: 2222
]
},
{
_leader:jack,
_member: []
}
]
}
I want to register a team member of Jack.
team_schema.findOneAndUpdate(
{team_year: '2015', 'team._leader' : 'jack'},
{$push: {
'team._member': req.body
}
},
function(err, post){
if (err) next(err);
res.end("success");
});
but it doesn't work.
Please help me.
I use
Node.js + express + MongoDB
I'm not good at English. T^T
You need to specify the index of the object for which you want to insert an object (nested array). For this you can use the positional operator ('$') provided by MongoDB. See here for more info.
So this query should work:
team_schema.findOneAndUpdate(
{team_year: '2015', 'team._leader' : 'jack'},
{$push: {
'team.$._member': req.body //The $ symbol resolves to an index from the query
}
},
function(err, post){
if (err) next(err);
res.end("success");
});

Using mongoose, how do I filter and then group by?

I am using mongoose and so far the query I use gets me all of the critiques based on a docId. I would like to group this result by distinct editors now. Except, my editors is an object.
This is what my critique query looks like:
Critique.find({docId:req.params.docId}).populate('editor', 'name username').exec(function(err, critiques){
if(err){
console.error("Cannot find critiques with docId: " + critiques.docId);
}
console.log(critiques);
res.jsonp(critiques);
});
This is my model I am querying:
var CritiqueSchema = new Schema({
className : String,
content: String,
eleId: Number,
type: String,
comments: String,
isAccepted: Boolean,
classes: String,
docId:{
type: Schema.ObjectId,
ref: 'Composition'
},
created: {
type: Date,
default: Date.now
},
editor: {
type: Schema.ObjectId,
ref: 'User'
},
});
UPDATE new query:
Critique.aggregate(
[ {$match : {docId : mongoose.Types.ObjectId(req.params.docId)}},
{$group : { _id : "$editor", critiques: { $push: "$$ROOT" } } }
]).exec(function(error, result){
if(!error)console.log(result);
else console.log(error);
});
What you need is $group in the aggregation framework. But aggregation and population don't go along. So you have two options populate and group the results by yourself by writing a loop or you can use $group to group them and then query each editor manually. The second is better as there will no duplication in editor queries whereas in population there will be significant duplication going on.
Critique.aggregate(
[{
$match:
{
docId: ObjectId(req.params.docid)
}
},
{ $group : { _id : "$editor", critiques: { $push: "$$ROOT" } } }
],
function(err,result){
if(!err){
/* result will be of the form:
[{_id:<an editor's objectid>,critiques:[{<critique1 document>},{<critique2 document>}...]}...]
*/
//you will have to manually query for each distinct editor(result[i]._id) which sucks
//because the call will be asynchronous in the loop and you can't send your response without using async library
//another option would be to use the $in operator on an array of the distinct critiques:
var editors = result.map(function(x) { return x._id } );
User.find({_id:{$in:editors}},{'username':1},function(err,editorDocs){
var editor_ids=editorDocs.map(function(x){return x._id})
var index;
for(var i=0;i<result.length;i++){
index=editor_ids.indexOf(result[i]._id);
result[i].editor=editorDocs[index].username;
}
//result is your final result. In the editor field of each object you will have the username of the editor
})
}
})
Check the docs for $$ROOT.

Resources