I'm trying to use $setUnion in an aggregation query, it works just fine in the MongoDB console but when I try to do the same in my Node app with Mongoose, it fails with this error:
{
[MongoError: exception: invalid operator '$setUnion']
name: 'MongoError',
errmsg: 'exception: invalid operator \'$setUnion\'',
code: 15999,
ok: 0
}
Here is a simplified query that I tried and still failed in the app:
db.users.aggregate(
{
$match: { _id: ObjectId("52c9375c497ed763f41941bd") }
},
{
$project: {
activities: {
$setUnion: [[1,2], [3,4]]
}
}
}
);
Any ideas what I could be doing wrong?
MongoDB: 2.6.6, Mongoose: 3.8.21, Node: 0.10.26
Update: here is the Mongoose code
var User = require('./providers/db').User,
mongoose = require('mongoose');
User.aggregate({
$match: { _id: mongoose.Types.ObjectId("52c9375c497ed763f41941bd") }
},
{
$project: {
activities: {
$setUnion: [[1,2], [3,4]]
}
}
}, function (err, result) {
if (err) { return console.log(err); }
else { console.log(result); }
});
Really don't see the point here and did ask you to look at it. And cannot say I didn't warn you several times. I believe the term you are looking for is "thank you very much":
var testSchema = new Schema({},{ "strict": false });
var User = mongoose.model( "User", testSchema, "test" );
mongoose.connect("mongodb://localhost/test");
async.series(
[
function(callback) {
User.remove({},callback);
},
function(callback) {
User.create({ "a": 1 },callback);
},
function(callback) {
User.aggregate(
[
{ "$project": {
"activities": {
"$setUnion": [[1,2],[3,4]]
}
}}
],
function(err,result) {
if (err) throw err;
console.log( result );
process.exit();
}
);
}
],
function(err) {
if (err) throw err;
}
);
And returns :
[ { _id: 54b4c13decda256710226134, activities: [ 1, 4, 3, 2 ] } ]
just as would be expected.
Related
Consider the query in Mongoose :
let StudentCodes = .... // getting this from somewhere
await Students.aggregate(
[
{
$project: {
StudentCODE: "$StudentCODE",
StudName: "$StudName",
StudProfileDesc: "$StudProfileDesc",
IsReviewed: {
$cond: [{ $eq: [StudentCodes, "$StudentCODE"] }, 1, 0]
}
}
}
],
function(err, results) {
if (err) {
console.log(err);
}
console.log(results);
return res.status(200).json(results);
}
);
How can We project IsReviewed as true or false if the property StudentCODE exists in the array StudentCodes ?
Try as below, you can use $in in $cond to do that :
let StudentCodes = .... // getting this from somewhere
await Students.aggregate(
[
{
$project: {
StudentCODE: "$StudentCODE",
StudName: "$StudName",
StudProfileDesc: "$StudProfileDesc",
IsReviewed: {
$cond: [{ $in: ["$StudentCODE", StudentCodes] }, true, false]
}
}
}
],
function (err, results) {
if (err) {
console.log(err);
}
console.log(results);
return res.status(200).json(results);
}
);
I have a query that fetches the top 5 people for a leaderboard. In robomongo this query works fine.
When I do something like
var leaderboard = User.find({points: {$exists: true}}, {
"firstname": 1,
"lastname": 1,
"points": 1
}).sort({points : -1}).limit(5)
console.log('leaderboard');
I get a lot of meaningless json with [object] almost everywhere.
How would I execute this query for use with mongoose + express so I can pass through to the view as an array of
firstname, lastname, points
So I can loop it through in the view?
My complete code is
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
User.find({}, function(err, docs) {
// console.log(docs);
});
// Get total points after submit
var leaderboard = User.find({points: {$exists: true}}, {
"firstname": 1,
"lastname": 1,
"points": 1
}).sort({points : -1}).limit(5).toArray();
console.log(leaderboard);
User.find({
points: {
$exists: true
}
}, function(err, docs) {
if(err){
console.log(err);
//do error handling
}
//if no error, get the count and render it
var count = 0;
for (var i = 0; i < docs.length; i++) {
count += docs[i].points;
}
var totalpoints = count;
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: totalpoints
});
});
} else {
// not logged in
return res.redirect('/login');
}
});
So you are really only returning a cursor here and not executing the query. You can of course always nest the queries, but you can be a bit nicer and use async.waterfall to avoid the indenting mess.
Also I would use .aggregate() rather than looping all the documents just to get a total. And mongoose automatically converts results to an array, so the .toArray() is not required here:
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
async.waterfall(
[
function(callback) {
User.find(
{ "points": { "$exists": true } },
{
"firstname": 1,
"lastname": 1,
"points": 1
}
).sort({points : -1}).limit(5).exec(callback);
},
function(leaderboard,callback) {
User.aggregate(
[
{ "$match": { "points": { "$exists": true } }},
{ "$group": {
"_id": null,
"totalpoints": { "$sum": "$points" }
}}
],
function(err,result) {
callback(err,result,leaderboard)
}
);
}
],
function(err,result,leaderboard) {
if (err) {
console.log(err);
//do error handling
} else {
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: result[0].totalpoints,
leaderboard: leaderboard
});
}
}
);
} else {
// not logged in
return res.redirect('/login');
}
});
So you get your leaderboard result and just put it in the response, much as is done in the example here.
An alternate approach is using async.parallel, since you don't need the output of the first query within the second. In that case the results of both queries are sent to the callback at the end, much like above. Again you just use the results in your final response.
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
async.parallel(
{
"leaderboard": function(callback) {
User.find(
{ "points": { "$exists": true } },
{
"firstname": 1,
"lastname": 1,
"points": 1
}
).sort({points : -1}).limit(5).exec(callback);
},
"totalpoints":function(callback) {
User.aggregate(
[
{ "$match": { "points": { "$exists": true } }},
{ "$group": {
"_id": null,
"totalpoints": { "$sum": "$points" }
}}
],
function(err,result) {
callback(err,result[0].totalpoints)
}
);
}
},
function(err,results) {
if (err) {
console.log(err);
//do error handling
} else {
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: results.totalpoints,
leaderboard: results.leaderboard
});
}
}
);
} else {
// not logged in
return res.redirect('/login');
}
});
I'm using Mongoose (MongoDB in node.js), and after reading this answer:
Replace value in array
I have another question:
Is it possible to do in the same sentence: push element into array or replace if this element is existing in the array?
Maybe something like this? (The example doesn't work)
Model.findByIdAndUpdate(id,
{
$pull: {"readers": {user: req.user.id}},
$push:{"readers":{user: req.user.id, someData: data}}
},{multi:true},callback)
Message error:
errmsg: 'exception: Cannot update \'readers\' and \'readers\' at the same time
Reference:
https://stackoverflow.com/a/15975515/4467741
Thank you!
Multiple operations on the same property path are simply not allowed in a single request, with the main reason being that the operations themselves have "no particular order" in the way the engine assigns them as the document is updated, and therefore there is a conflict that should be reported as an error.
So the basic abstraction on this is that you have "two" update operations to perform, being one to "replace" the element where it exists, and the other to "push" the new element where it does not exist.
The best way to implement this is using "Bulk" operations, which whilst still "technically" is "two" update operations, it is however just a "single" request and response, no matter which condition was met:
var bulk = Model.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": req.user.id }).updateOne({
"$set": { "readers.$.someData": data } }
});
bulk.find({ "_id": id, "readers.user": { "$ne": req.user.id } }).updateOne({
"$push": { "readers": { "user": req.user.id, "someData": data } }
});
bulk.execute(function(err,result) {
// deal with result here
});
If you really "need" the updated object in result, then this truly becomes a "possible" multiple request following the logic where the array element was not found:
Model.findOneAndUpdate(
{ "_id": id, "readers.user": req.user.id },
{ "$set": { "readers.$.someData": data } },
{ "new": true },
function(err,doc) {
if (err) // handle error;
if (!doc) {
Model.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": req.user.id } },
{ "$push": { "readers":{ "user": req.user.id, "someData": data } } },
{ "new": true },
function(err,doc) {
// or return here when the first did not match
}
);
} else {
// was updated on first try, respond
}
}
);
And again using you preferred method of not nesting callbacks with either something like async or nested promise results of some description, to avoid the basic indent creep that is inherrent to one action being dependant on the result of another.
Basically probably a lot more efficient to perform the updates in "Bulk" and then "fetch" the data afterwards if you really need it.
Complete Listing
var async = require('async'),
mongoose = require('mongoose')
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var userSchema = new Schema({
name: String
});
var dataSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
someData: String
},{ "_id": false });
var testSchema = new Schema({
name: String,
readers: [dataSchema]
});
var User = mongoose.model( 'User', userSchema ),
Test = mongoose.model( 'Test', testSchema );
var userId = null,
id = null;
async.series(
[
// Clean models
function(callback) {
async.each([User,Test],function(model,callback) {
model.remove({},callback);
},callback);
},
// Create a user
function(callback) {
User.create({ name: 'bill' },function(err,user) {
userId = user._id;
callback(err);
});
},
function(callback) {
Test.create({ name: 'Topic' },function(err,topic) {
id = topic._id;
console.log("initial state:");
console.log(topic);
callback(err);
});
},
// 1st insert array 2nd update match 1 modified
function(callback) {
var bulk = Test.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": userId }).updateOne({
"$set": { "readers.$.someData": 1 }
});
bulk.find({ "_id": id, "readers.user": { "$ne": userId }}).updateOne({
"$push": { "readers": { "user": userId, "someData": 1 } }
});
bulk.execute(function(err,result) {
if (err) callback(err);
console.log("update 1:");
console.log(JSON.stringify( result, undefined, 2));
Test.findById(id,function(err,doc) {
console.log(doc);
callback(err);
});
});
},
// 2nd replace array 1st update match 1 modified
function(callback) {
var bulk = Test.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": userId }).updateOne({
"$set": { "readers.$.someData": 2 }
});
bulk.find({ "_id": id, "readers.user": { "$ne": userId }}).updateOne({
"$push": { "readers": { "user": userId, "someData": 2 } }
});
bulk.execute(function(err,result) {
if (err) callback(err);
console.log("update 2:");
console.log(JSON.stringify( result, undefined, 2));
Test.findById(id,function(err,doc) {
console.log(doc);
callback(err);
});
});
},
// clear array
function(callback) {
Test.findByIdAndUpdate(id,
{ "$pull": { "readers": {} } },
{ "new": true },
function(err,doc) {
console.log('cleared:');
console.log(doc);
callback(err);
}
);
},
// cascade 1 inner condition called on no array match
function(callback) {
console.log('update 3:');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": userId },
{ "$set": { "readers.$.someData": 1 } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
if (!doc) {
console.log('went inner');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": userId } },
{ "$push": { "readers": { "user": userId, "someData": 1 } } },
{ "new": true },
function(err,doc) {
console.log(doc)
callback(err);
}
);
} else {
console.log(doc);
callback(err);
}
}
);
},
// cascade 2 outer condition met on array match
function(callback) {
console.log('update 3:');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": userId },
{ "$set": { "readers.$.someData": 2 } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
if (!doc) {
console.log('went inner');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": userId } },
{ "$push": { "readers": { "user": userId, "someData": 2 } } },
{ "new": true },
function(err,doc) {
console.log(doc)
callback(err);
}
);
} else {
console.log(doc);
callback(err);
}
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Output:
initial state:
{ __v: 0,
name: 'Topic',
_id: 55f60adc1beeff6b0a175e98,
readers: [] }
update 1:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { user: 55f60adc1beeff6b0a175e97, someData: '1' } ] }
update 2:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { user: 55f60adc1beeff6b0a175e97, someData: '2' } ] }
cleared:
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [] }
update 3:
went inner
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { someData: '1', user: 55f60adc1beeff6b0a175e97 } ] }
update 3:
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { someData: '2', user: 55f60adc1beeff6b0a175e97 } ] }
I find many great answers here on SO like this answer an this. But i can not get it to work...
I tried ObjectId("55cf816559d2fc8d0e6c14a8") in the query where the id is.
This query works when robotmongo run it:
db.getCollection('events').update(
{ "_id": ObjectId("55cf816559d2fc8d0e6c14a8") },
{ "$pull": { "workers" : { "_id": ObjectId("55cf89ac7cba1d0a10ca86c7")}}},
false,
true
)
Side note, what is the false,true for?
Here is my current code
event.update(
{'_id': "55cf816559d2fc8d0e6c14a8"},
{ "$pull": { "workers" : {_id: "55cf89ac7cba1d0a10ca86c7"}}},
function(err, result) {
console.log(err);
console.log(result);
}
);
I do not get any errors and the result is equal to 1.
Works for me. You must be doing something differently and incorrectly:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var workerSchema = new Schema({
name: String
});
var eventSchema = new Schema({
name: String,
workers: [workerSchema]
});
var Event = mongoose.model( 'Event', eventSchema );
async.series(
[
function(callback) {
Event.remove({},function(err) {
callback(err);
});
},
function(callback) {
var event = new Event({
_id: "55cf816559d2fc8d0e6c14a8",
name: "Great thing"
});
event.workers.push({
_id: "55cf89ac7cba1d0a10ca86c7",
name: "Worker1"
});
event.save(function(err,event) {
console.log(event);
callback(err);
});
},
function(callback) {
Event.findOneAndUpdate(
{ "_id": "55cf816559d2fc8d0e6c14a8" },
{ "$pull": { "workers": { "_id": "55cf89ac7cba1d0a10ca86c7" } } },
{ "new": true },
function(err,event) {
console.log(event)
callback(err);
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
With the expected output:
{ __v: 0,
_id: 55cf816559d2fc8d0e6c14a8,
name: 'Great thing',
workers: [ { _id: 55cf89ac7cba1d0a10ca86c7, name: 'Worker1' } ] }
{ _id: 55cf816559d2fc8d0e6c14a8,
name: 'Great thing',
__v: 0,
workers: [] }
I am trying to adapt the full text search made here to work with meteor. I exported the mongodb url to one running 2.6.1. to make full text search compatible but I am getting these errors server/search.js:2:15: Unexpected token .andserver/search.js:42:7: Unexpected token ). What am I missing?
server.js
Meteor.methods({
Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
)
};
Meteor.methods({
feedupdate: function(req) {
Posts.find({
"$text": {
"$search": req
}
}, {
smaintext: 1,
submitted: 1,
_id: 1,
Posts: {
$meta: "Posts"
}
}, {
sort: {
textScore: {
$meta: "posts"
}
}
}).toArray(function(err, items) {
for (e=0;e<101;e++) {
Meteor.users.update({
"_id": this.userId
}, {
"$addToSet": {
"profile.search": item[e]._id
}
});
}
})
}
)
};
This is wrong definition of method
Meteor.methods({
Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
)
};
you must specify a method name ( http://docs.meteor.com/#/basic/Meteor-methods )
So it will be something like this
Meteor.methods({
myMethodName : function() { Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
}
});
in second method there is a semicron and parenthise problem.
Correct version is
Meteor.methods({
feedupdate: function(req) {
Posts.find({
"$text": {
"$search": req
}
}, {
smaintext: 1,
submitted: 1,
_id: 1,
Posts: {
$meta: "Posts"
}
}, {
sort: {
textScore: {
$meta: "posts"
}
}
}).toArray(function(err, items) {
for (e=0;e<101;e++) {
Meteor.users.update({
"_id": this.userId
}, {
"$addToSet": {
"profile.search": item[e]._id
}
});
}
});
}
});