[
{
"_id":"56569bff5fa4f203c503c792",
"Status":{
"StatusID":2,
"StatusObjID":"56559aad5fa4f21ca8492277",
"StatusValue":"Closed"
},
"OwnerPractice":{
"PracticeObjID":"56559aad5fa4f21ca8492291",
"PracticeValue":"CCC",
"PracticeID":3
},
"Name":"AA"
},
{
"_id":"56569bff5fa4f203c503c792",
"Status":{
"StatusID":2,
"StatusObjID":"56559aad5fa4f21ca8492277",
"StatusValue":"Open"
},
"OwnerPractice":{
"PracticeObjID":"56559aad5fa4f21ca8492292",
"PracticeValue":"K12",
"PracticeID":2
},
"Name":"BB"
}
]
In above json response,
How to group by PracticeValue,StatusValue into single function,
the below code to be used to group only StatusValue,please help how to group Practice value with the same function,
Opp.aggregate([
{$group: {
_id: '$Status.StatusValue',
count: {$sum: 1}
}}
], function (err, result) {
res.send(result);
});
and my response is,
[
{
"_id":"Deleted",
"count":0
},
{
"_id":"Open",
"count":1
},
{
"_id":"Closed",
"count":1
}
]
please help me, how to use more then $group function..
You can group by multiple fields like this:
var resultAggr = {Status: [], Practice: []};
Opp.aggregate(
[
{$group: { _id: '$Status.StatusValue', count: {$sum: 1} }}
], function (err, statusResult) {
resultAggr.Status = statusResult;
Opp.aggregate(
[
{$group: { _id: '$OwnerPractice.PracticeValue', count: {$sum: 1} }}
], function (err, practiceResult) {
resultAggr.Practice = practiceResult;
res.send([resultAggr])
});
});
Related
I'm trying to build some advanced hello world app on top of express and mongoose. Assume I have next Schemas:
const pollOptionsSchema = new Schema({
name: String,
votes: {
type: Number,
default: 0
}
});
const pollSchema = new Schema({
name: String,
dateCreated: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId },
options: [pollOptionsSchema]
});
And when I simply call
Poll.findOne({_id: req.params.id}).exec((err, data) => {
if (err) console.log(err);
// I receive next data:
// { _id: 58ef3d2c526ced15688bd1ea,
// name: 'Question',
// author: 58dcdadfaea29624982e2fc6,
// __v: 0,
// options:
// [ { name: 'stack', _id: 58ef3d2c526ced15688bd1ec, votes: 5 },
// { name: 'overflow', _id: 58ef3d2c526ced15688bd1eb, votes: 3 } ],
// dateCreated: 2017-04-13T08:56:12.044Z }
});
The question is how I could receive same data + aggregated number of votes (i.e 8 in case above) after calling some method on Model level, for example:
// I want to receive:
// { _id: 58ef3d2c526ced15688bd1ea,
// name: 'Question',
// author: 58dcdadfaea29624982e2fc6,
// __v: 0,
// totalNumberOfVotes: 8,
// options:
// [ { name: 'stack', _id: 58ef3d2c526ced15688bd1ec, votes: 5 },
// { name: 'overflow', _id: 58ef3d2c526ced15688bd1eb, votes: 3 } ],
// dateCreated: 2017-04-13T08:56:12.044Z }
Or maybe I need to implement some extra method on document level i.e (data.aggregate)?
I've already reviewed:
http://mongoosejs.com/docs/api.html#model_Model.mapReduce
http://mongoosejs.com/docs/api.html#aggregate_Aggregate
https://docs.mongodb.com/manual/core/map-reduce/
https://docs.mongodb.com/manual/tutorial/map-reduce-examples/
But can't utilize it for my case :(
Any advice will be much appreciated. Thanks!
Use $reduce operator within an $addFields pipeline to create the totalNumberOfVotes field. In your aggregate pipeline, the first step is the $match which filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage and uses standard MongoDB queries.
Consider running the following aggregate operation to get the desired result:
Poll.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{
"$addFields": {
"totalNumberOfVotes": {
"$reduce": {
"input": "$options",
"initialValue": 0,
"in": { "$add" : ["$$value", "$$this.votes"] }
}
}
}
}
]).exec((err, data) => {
if (err) console.log(err);
console.log(data);
});
NB: The above will work for MongoDB 3.4 and greater.
For other earlier versions you would need to $unwind the options array first before grouping the denormalised documents within a $group pipeline step and aggregating with the accumulators $sum, $push and $first.
The following example shows this approach:
Poll.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{ "$unwind": { "path": "$options", "preserveNullAndEmptyArrays": true } },
{
"$group": {
"_id": "$_id",
"totalNumberOfVotes": { "$sum": "$options.votes" },
"options": { "$push": "$options" },
"name": { "$first": "$name" },
"dateCreated": { "$first": "$dateCreated" },
"author": { "$first": "$author" }
}
}
]).exec((err, data) => {
if (err) console.log(err);
console.log(data);
});
I simply want to count the element in array based on the query. I tried the following command but not solved my problem.
I want to count the element whose TimeStamp is in between "2017-02-17T18:30:00.000Z and "2017-02-18T18:29:59.999Z" on DATA2 array, but it returns only 1.
CODE Executed:
CODE 1
db.ABC.aggregate([{
$match: {
$and: [{
DATA2: {
$exists: true
}
}, {
"DATA2.TimeStamp": {
$gte: require('../../modules/getDates').getFromDate(item),
$lte: require('../../modules/getDates').getToDate(item)
}
}, {
Client_id: "123" /*req.query.client_id*/
}]
}
}, {
$project: {
DATASiz: {
$size: "$DATA2"
},
"has bananas": {
$in: ["DATA2.$.TimeStamp"]
}
}
}], function(err, result) {
console.log(result)
callBack();
})
Code 2
db.abc.find({ $and:[{DATA2: {$exists: true}},{Client_id: "123"},{"DATA2": { $elemMatch: { TimeStamp: { $gte: require('../../modules/getDates').getFromDate(item), $lte: require('../../modules/getDates').getToDate(item) } } }}]
}, function(err, result) {
console.log(JSON.stringify(result))
callBack();
})
Code 3
//db.abc.find //also tried
db.abc.count({
$and: [{
DATA2: {
$exists: true
}
}, {
"DATA2.TimeStamp": {
$gte: require('../../modules/getDates').getFromDate(item),
$lte: require('../../modules/getDates').getToDate(item)
}
}, {
Client_id: "123" /*req.query.client_id*/
}]
},{
"DATA2.$":1
}, function(err, result) {
console.log(result)
callBack();
})
JSON Format:
{
"_id": {
"$oid": "57c7404985737e2c78fde6b3"
},
"ABC": "1304258470",
"Status": "Not Found",
"DATA1": [
{123},{123},{123}
],
"Remark": "Not Found",
"DATA2": [
{
"TimeStamp": "2017-02-18T09:01:43.060Z",
"NdrStatus": "Door Locked",
},
{
"TimeStamp": "2017-02-18T08:09:43.347Z",
"NdrStatus": "HOLD",
},
{
"TimeStamp": "2017-02-20T08:09:43.347Z",
"NdrStatus": "HOLD",
}
]
}
Result:
I am getting the first element of DATA2 using CODE 3 but I know that as per the query 2 elements are to return.
I expect 2 as in count.
Also used $unwind $redact
Thanks in advance.
You can use the $filter and $size operators for this:
var start = require('../../modules/getDates').getFromDate(item),
end = require('../../modules/getDates').getToDate(item);
db.ABC.aggregate([
{
"$match": {
"DATA2": { "$exists": true },
"DATA2.TimeStamp": { "$gte": start, "$lte": end },
"Client_id": "123"
}
},
{
"$project": {
"DATASiz": {
"$size": {
"$filter": {
"input": "$DATA2",
"as": "item",
"cond": {
"$and": [
{ "$gte": ["$$item.TimeStamp", start] },
{ "$lte": ["$$item.TimeStamp", end] }
]
}
}
}
}
}
}
], function(err, result) {
console.log(result);
callBack();
});
I have the following collection:
{
_id: 1,
docs: [{
name: file1,
labels: [label1, label2, label3, label4]
},{
name: file2,
labels: [label3, label4]
},{
name: file3,
labels: [label1, label3, label4]
}]
}
When a user searches for labels, I need to get all the docs that have those labels.
So if a user searches for "label1" and "label3", I need to get "file1" and "file3". At the moment I have the following code:
var collection = db.collection(req.user.username);
collection.aggregate([
// Unwind each array
{ "$unwind": "$docs" },
{ "$unwind": "$docs.labels" },
// Filter just the matching elements
{
"$match": {
"docs.labels": { "$all": ["label1", "label3"] }
}
}
]).toArray(function (err, items) {
res.send(items);
});
This works fine if I only search for "label1" or "label3", but not the two together. Can you please help me with this, since I haven't found an answer that works myself.
You can efficiently do this by $filtering your documents in the $project stage.
let inputLabels = ["label1", "label3"];
collection.aggregate([
{ "$match": { "docs.labels": { "$all": inputLabels }}},
{ "$project": {
"docs": {
"$filter": {
"input": "$docs",
"as": "doc",
"cond": { "$setIsSubset": [ inputLabels, "$$doc.labels" ] }
}
}
}}
])]).toArray(function (err, items) {
res.send(items);
});
How to pass an parameter to the aggregation?
I'm getting the params and trying to pass it using $match operator but query returns empty array:
app.get('/api/:name', function(req, res){
var name = req.params.name;
console.log(name);
db.collection('coll').aggregate([{$match: {name: '$name'}}, {$unwind: { path: "$dates", includeArrayIndex: "idx" } }, { $project: { _id: 0, dates: 1, numbers: { $arrayElemAt: ["$numbers", "$idx"] }, goals: { $arrayElemAt: ["$goals", "$idx"] }, durations: { $arrayElemAt: ["$durations", "$idx"]}}}]).toArray(function(err, docs) {
if (err) {
assert.equal(null);
}
else {
console.log(docs);
res.json(docs);
}
});
})
Should I care about order of the operators in pipeline?
Try the following code:-
app.get('/api/:name', function(req, res){
var name = req.params.name;
var query = [{$match: {'name': name}}, {$unwind: { path: "$dates", includeArrayIndex: "idx" } }, { $project: { _id: 0, dates: 1, numbers: { $arrayElemAt: ["$numbers", "$idx"] }, goals: { $arrayElemAt: ["$goals", "$idx"] }, durations: { $arrayElemAt: ["$durations", "$idx"]}}}];
db.collection('coll').aggregate(query).toArray(function(err, docs) {
if (err) {
assert.equal(null);
}
else {
console.log(docs);
res.json(docs);
}
});
})
It seems you never use variable called name.
Try this, change {$match: {name: '$name'} to {$match: {name: name}.
try,
{$match: {'name': req.params.name}}
this works for me
Mongoose does not cast pipeline stages. The below will not work unless _id is a string in the database
new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
// Do this instead to cast to an ObjectId
new Aggregate([{ $match: { _id:
mongoose.Types.ObjectId('00000000000000000000000a') } }]);
API URL: https://mongoosejs.com/docs/api.html#aggregate_Aggregate
var UserSchema = Schema (
{
android_id: String,
created: {type: Date, default:Date.now},
interests: [{ type: Schema.Types.ObjectId, ref: 'Interests' }],
});
Users.aggregate([
{ $match: {android_id: {$ne: userID}, interests: {$elemMatch: {$in: ids}} }},
{ $group: { _id: { android_id: '$android_id'},count: {$sum: 1}}},
{ $sort: {count: -1}},
{ $limit: 5 }],
I need the to find the top 5 android_ids of the users with the most interests in common with me (ids array). I can work with the array of only matched elements from the interests array too.
You seemed to be going along the right lines here but you do need to consider that arrays have special considerations for comparisons.
Your basic start here is to find all users that are not the current user, and that you also need at least the "interests" array of the current user as well. You seem to be doing that already, but for here let us consider that you have the whole user object for the current user which will be used in the listing.
This makes your "top 5" basically a product of "Not me, and the most interests in common", which means you basically need to count the "overlap" of interests on each user compared to the current user.
This is basically the $setIntersection of the two arrays or "sets" where the elements in common are returned. In order to count how many are in common, there is also the $size operator. So you apply like this:
Users.aggregate(
[
{ "$match": {
"android_id": { "$ne": user.android_id },
"interests": { "$in": user.interests }
}},
{ "$project": {
"android_id": 1,
"interests": 1,
"common": {
"$size": {
"$setIntersection": [ "$interests", user.interests ]
}
}
}},
{ "$sort": { "common": -1 } },
{ "$limit": 5 }
],
function(err,result) {
}
);
The result returned in "common" is the count of common interests between the current user and the user being examined in the data. This data is then processed by $sort in order to put the largest number of common interests on top, and then $limit returns only the top 5.
If for some reason your MongoDB version is presently lower than MongoDB 2.6 where both the $setIntersection and $size operators are introduced, then you can still do this, but it just takes a longer form of processing the arrays.
Mainly you need to $unwind the arrays and process each match individually:
{ "$match": {
"android_id": { "$ne": user.android_id },
"interests": { "$in": user.interests }
}},
{ "$unwind": "$interests" },
{ "$group": {
"_id": "$_id",
"android_id": { "$first": "$android_id" },
"interests": { "$push": "$interests" },
"common": {
"$sum": {
"$add": [
{ "$cond": [{ "$eq": [ "$interests", user.interests[0] ] },1,0 ] },
{ "$cond": [{ "$eq": [ "$interests", user.interests[1] ] },1,0 ] },
{ "$cond": [{ "$eq": [ "$interests", user.interests[2] ] },1,0 ] }
]
}
}
}},
{ "$sort": { "common": -1 }},
{ "$limit": 5 }
Which is more practically coded to generate the condtional matches in the pipeline:
var pipeline = [
{ "$match": {
"android_id": { "$ne": user.android_id },
"interests": { "$in": user.interests }
}},
{ "$unwind": "$interests" }
];
var group =
{ "$group": {
"_id": "$_id",
"android_id": { "$first": "$android_id" },
"interests": { "$push": "$interests" },
"common": {
"$sum": {
"$add": []
}
}
}};
user.interests.forEach(function(interest) {
group.$group.common.$sum.$add.push(
{ "$cond": [{ "$eq": [ "$interests", interest ] }, 1, 0 ] }
);
});
pipeline.push(group);
pipeline = pipeline.concat([
{ "$sort": { "common": -1 }},
{ "$limit": 5 }
])
User.aggregate(pipeline,function(err,result) {
});
The key elements there being that "both" the current user and the user being inspected have their "interests" separated out for comparison to see if they are "equal". The result from $cond attributes a 1 where this is true or 0 where false.
Any returns ( and only ever expected to be 1 at best, per pair ) are passed to the $sum accumulator which counts the matches in common. You can alternately $match with an $in condition again:
{ "$unwind": "$interests" },
{ "$match": { "interests": { "$in": user.interests } },
{ "$group": {
"_id": "$_id",
"android_id": { "$first": "$android_id" },
"common": { "$sum": 1 }
}}
But this is naturally destructive of the array content as non matches are filtered out. So it depends on what you would rather have in the response.
That is the basic process for getting the "common" counts for use in further processing like $sort and $limit in order to get your "top 5".
Just for fun, here is a basic node.js listing to show the effects of common matches:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/sample');
var interestSchema = new Schema({
name: String
});
var userSchema = new Schema({
name: String,
interests: [{ type: Schema.Types.ObjectId, ref: 'Interest' }]
});
var Interest = mongoose.model( 'Interest', interestSchema );
var User = mongoose.model( 'User', userSchema );
var interestHash = {};
async.series(
[
function(callback) {
async.each([Interest,User],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
async.each(
[
"Tennis",
"Football",
"Gaming",
"Cooking",
"Yoga"
],
function(interest,callback) {
Interest.create({ name: interest},function(err,obj) {
if (err) callback(err);
interestHash[obj.name] = obj._id;
callback();
});
},
callback
);
},
function(callback) {
async.each(
[
{ name: "Bob", interests: ["Tennis","Football","Gaming"] },
{ name: "Tom", interests: ["Football","Cooking","Yoga"] },
{ name: "Sue", interests: ["Tennis","Gaming","Yoga","Cooking"] }
],
function(data,callback) {
data.interests = data.interests.map(function(interest) {
return interestHash[interest];
});
User.create(data,function(err,user) {
//console.log(user);
callback(err);
})
},
callback
);
},
function(callback) {
async.waterfall(
[
function(callback) {
User.findOne({ name: "Bob" },callback);
},
function(user,callback) {
console.log(user);
User.aggregate(
[
{ "$match": {
"_id": { "$ne": user._id },
"interests": { "$in": user.interests }
}},
{ "$project": {
"name": 1,
"interests": 1,
"common": {
"$size": {
"$setIntersection": [ "$interests", user.interests ]
}
}
}},
{ "$sort": { "common": -1 } }
],
function(err,result) {
if (err) callback(err);
Interest.populate(result,'interests',function(err,result) {
console.log(result);
callback(err);
});
}
);
}
],
callback
);
}
],
function(err) {
if (err) throw err;
//console.dir(interestHash);
mongoose.disconnect();
}
);
Which will output:
{ _id: 55dbd7be0e5516ac16ea62d1,
name: 'Bob',
__v: 0,
interests:
[ 55dbd7be0e5516ac16ea62cc,
55dbd7be0e5516ac16ea62cd,
55dbd7be0e5516ac16ea62ce ] }
[ { _id: 55dbd7be0e5516ac16ea62d3,
name: 'Sue',
interests:
[ { _id: 55dbd7be0e5516ac16ea62cc, name: 'Tennis', __v: 0 },
{ _id: 55dbd7be0e5516ac16ea62ce, name: 'Gaming', __v: 0 },
{ _id: 55dbd7be0e5516ac16ea62d0, name: 'Yoga', __v: 0 },
{ _id: 55dbd7be0e5516ac16ea62cf, name: 'Cooking', __v: 0 } ],
common: 2 },
{ _id: 55dbd7be0e5516ac16ea62d2,
name: 'Tom',
interests:
[ { _id: 55dbd7be0e5516ac16ea62cd, name: 'Football', __v: 0 },
{ _id: 55dbd7be0e5516ac16ea62cf, name: 'Cooking', __v: 0 },
{ _id: 55dbd7be0e5516ac16ea62d0, name: 'Yoga', __v: 0 } ],
common: 1 } ]