MongoDB nested collection query of same collection - mongodb

How do you do a nested query when you need the results of the same collection in the next query?
var mongo = require('../config/mongo');
var mongoDB = mongo.db;
...
exports.myFunction = function(req, res) {
...
...
// e.g. myArray = ['a','b','c'];
mongoDB.collection('MyCollection', function(err, collection) {
collection.find({ $or: [{ 'source': {$in: myArray} },{ 'target': {$in: myArray} }]}, { "someVar": 0}).toArray(function(err, firstResults) {
var allResults = [];
for (var i = 0; i < firstResults.length; i++) {
allResults[firstResults[i].source]=1;
allResults[firstResults[i].target]=1;
};
var secondResults = Object.keys(allResults);
mongoDB.collection('MyCollection', function(err, collection) {
collection.find({ $or: [{ 'source': {$in: secondResults} },{ 'target': {$in: secondResults} }]}, { "someVar": 0}).toArray(function(err, items) {
res.send(items);
});
});
});
});
But it doesn't like that I am calling the same collection 'MyCollection' twice. I'm trying to get any document whose source or target involves secondResults.

You should get rid of the second call to open 'MyCollection'. You already have a 'MyCollection' object after opening it once. Namely, you have this:
mongoDB.collection('MyCollection', function(err, collection) {
collection.find({ $or: [{ 'source': {$in: secondResults} },{ 'target': {$in: secondResults} }]}, { "someVar": 0}).toArray(function(err, items) {
res.send(items);
});
});
but you already have an instance of 'MyCollection' from here:
mongoDB.collection('MyCollection', function(err, collection) {
collection.find({ $or: [{ 'source': {$in: myArray} },{ 'target': {$in: myArray} }]}, { "someVar": 0}).toArray(function(err, firstResults) {
So since the second call to 'collection' is in the scope of the first call, you can (and should) reuse that instance:
collection.find({ $or: [{ 'source': {$in: secondResults} },{ 'target': {$in: secondResults} }]}, { "someVar": 0}).toArray(function(err, items) {
res.send(items);
});
Alternatively, if you're using node-mongodb-native, you can have a look at some of the examples to see other ways of accomplishing what you're after.
UPDATE 1
This code works for me:
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db){
if(err) throw err;
var collection = db.collection('c');
var secondQuery = {};
collection.find({a:'b'}).toArray(function(err, firstRes){
for(var i = 0; i < firstRes.length; i++){
secondQuery[firstRes[i].a] = firstRes[i].b;
}
collection.find(secondQuery).toArray(function(err, secondRes){
for(var i = 0; i < secondRes.length; i++){
console.log(secondRes[i]);
}
db.close();
});
});
});
UPDATE 2
Note that the above code is using node-mongodb-native version 1.3.19. Since version 1.2, it is recommended to use MongoClient when interacting with mongodb from Node.js driver.

Related

How to use $elemMatch in kinvey-flex-sdk?

Is there any way, how to use $elemMatch Projection Operators in kinvey-flex-sdk.
You can try this way.
var dataStore = modules.dataStore({ skipBl: true, useMasterSecret: true });
var collection = dataStore.collection('<collectionName>');
var query = new Kinvey.Query();
query.equalTo('items', { $elemMatch: { item: 'a', qty: { $gte: 23 } } });
collection.find(query, function (error, result) {
});

mongoose populate and sort nested item

I have a simple model:
user = {
'items': [{
'name': 'abc',
'pages': [ObjectId("58c703a353dbaf37586b885c"), ObjectId("58c703a353dbaf37586b885d"), ..]}
}]
};
I'm trying to sort the pages of current item:
User.findOne({'_id': id}, {'items': {$elemMatch: {'_id': id2}}})
.populate({path: 'items.pages', select: '_id', options: { sort: { _id: -1 } }})
.exec(function(err, user) {
});
But I'm getting an error: Error: Cannot populate withsorton path items.pages because it is a subproperty of a document array. What should I change?

MongoDB, aggregation query an Multipoint geoindexes

I am trying to query a 2dsphere geo-index which has Multipoints in its coordinates. According to the mongodb documentation, it's a new feature from 2.6, and I did not find a good example for querying it.
My code works fine with a coordinate of Type Point, but not when I change it to MultiPoint.
I am using mongoose and nodejs.
This is the mongoose schema :
AnnonceSchema = mongoose.Schema({
[...]
geo: {type: Object, index: '2dsphere',name:String }
});
My search method :
AnnonceSchema.statics.search = function(long, lat, maxDistanceInKm, done){
var Annonce = this;
Annonce.aggregate(
[
{ $match: [...]
{ $match: { "geo": {$within: {$centerSphere: [
[long, lat],
maxDistanceInKm / 6371
]}}}},
{ $sort: { score: { $meta: "textScore" } } },
{ $project: { title: 1, _id: 1, score: { $meta: "textScore" }}},
{ $match: { score: { $gt: 1.0 } } }
], done);
};
My unit test:
var notReturned = {[...], geo:
{type: 'MultiPoint', coordinates:[[2.65, 48.79]]}};
var returned = {[...], geo: {type: 'Point', coordinates:[2.65, 48.79]}};
Model.create(doesNotWork, function(){
Model.create(doesWork, function(){
Annonce.searchAnnonce(2.65, 48.79, 30, function (err, annonces) {
assert.strictEqual(annonces.length, 2);
});
});
I don't know what I am missing, the coordinates are good, the 2 objects are the sames, the only thing that is different is their their type and that I put an array of arrays for MultiPoint.
Did anyone already tried this feature ? How to query MultiPoints geo indexes ?
Thank you

Mongoosejs populate and aggregate subdocuments

I have a '3-layered' relationship in MongooseJS like so, it's two one-to-many relationships between subdocuments. Like so:
var BroadcastSchema = new Schema({
...
_donationAddresses: [{
type: Schema.Types.ObjectId,
ref: 'DonationAddress'
}]
});
var DonationAddressSchema = new Schema({
...
_donations: [{
type: Schema.Types.ObjectId,
ref: 'Donation'
}]
});
var DonationSchema = new Schema({
...
amount: Number
});
I want to get the $sum total of the amount:Number on the DonationSchema
So far I've populated the Donation by using a work-around listed here (because as far as I know you can't populate a populate so far as I know)
Broadcast.find()
.exec(function(err, broadcasts) {
// this works
var iter = function(broadcast, callback) {
DonationAddress.populate(broadcast._donationAddresses, {
path: '_donations'
}, callback);
};
// tried to iterate over the donation address and
// aggregate the _donations.amount
var iter2 = function(broadcast, callback) {
DonationAddress.aggregate([{
$match: {
_id: broadcast._donationAddresses
}
}, {
$unwind: "$_donations"
}, {
$group: {
_id: "$_id",
total: {
$sum: "$_donations.amount"
}
}
}], callback);
};
async.each(broadcasts, iter, function done(err) {
async.each(broadcasts, iter2, function done(err) {
res.json(broadcasts);
});
});

Mongoose's find method with $or condition does not work properly

Recently I start using MongoDB with Mongoose on Nodejs.
When I use Model.find method with $or condition and _id field, Mongoose does not work properly.
This does not work:
User.find({
$or: [
{ '_id': param },
{ 'name': param },
{ 'nickname': param }
]
}, function(err, docs) {
if(!err) res.send(docs);
});
By the way, if I remove the '_id' part, this DOES work!
User.find({
$or: [
{ 'name': param },
{ 'nickname': param }
]
}, function(err, docs) {
if(!err) res.send(docs);
});
And in MongoDB shell, both work properly.
I solved it through googling:
var ObjectId = require('mongoose').Types.ObjectId;
var objId = new ObjectId( (param.length < 12) ? "123456789012" : param );
// You should make string 'param' as ObjectId type. To avoid exception,
// the 'param' must consist of more than 12 characters.
User.find( { $or:[ {'_id':objId}, {'name':param}, {'nickname':param} ]},
function(err,docs){
if(!err) res.send(docs);
});
I implore everyone to use Mongoose's query builder language and promises instead of callbacks:
User.find().or([{ name: param }, { nickname: param }])
.then(users => { /*logic here*/ })
.catch(error => { /*error logic here*/ })
Read more about Mongoose Queries.
You can also add a mix of $or and and to give your functions more flexibility, options and robustness, like so:
var ObjectId = require("mongoose").Types.ObjectId;
var idParam = new ObjectId(param.length < 12 ? "123456789012" : param);
const {nameParam, nicknameParam, ageParam} = req.params || req.body || req.query
User.find({
$or: [{
_id: objId
},
{
name: nameParam
},
{
nickname: nicknameParam
}
],
$and: [{
age: ageParam
}]
},
function (err, docs) {
if (!err) res.send(docs);
}
);
So this means that your find() will look for all users where (_id = idParam OR name = nameParam OR nickname = nicknameParam) AND (age = ageParam)
I hope this helps someone out there.
Cheers!!!