I am trying to create a form builder and generator and from generated forms, I will collect answers. After collecting answers I want to filter them according to some filter parameters and get average results for each category.
Questions have categories(attributeGroupOne&Two) bind to them.
I have the following structure for my answers.
{
"form": [{
"_id": {
"$oid": "62295d028b6b895048b7da96"
},
"questionType": "lickert5",
"label": "deneme sorusu 1",
"attributeGroupTwo": "deneme kategorisi alt 2",
"attributeGroupOne": "deneme kategorisi",
"name": "Question_1",
"weight": 4,
"type": "radio",
"options": {
"1": "Çok Az",
"2": "Az",
"3": "Bazen",
"4": "Genellikle",
"5": "Her Zaman"
}
}, {
"_id": {
"$oid": "62295d028b6b895048b7da95"
},
"questionType": "lickert10",
"label": "deneme sorusu 2",
"attributeGroupTwo": "deneme alt 3",
"attributeGroupOne": "deneme kategorisi",
"name": "Question_2",
"weight": 4,
"type": "radio",
"options": {
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "10"
}
}, {
"_id": {
"$oid": "62295d028b6b895048b7da94"
},
"questionType": "trueFalse",
"label": "deneme sorusu 3",
"attributeGroupTwo": "deneme alt 5",
"attributeGroupOne": "deneme kategorisi",
"name": "Question_3",
"weight": 4,
"type": "radio",
"options": {
"0": "Yanlış",
"10": "Doğru"
}
}, {
"_id": {
"$oid": "62295d028b6b895048b7da93"
},
"questionType": "lickert5",
"label": "deneme sorusu 4",
"attributeGroupTwo": "deneme main sub",
"attributeGroupOne": "deneme main 2",
"name": "Question_4",
"weight": 4,
"type": "radio",
"options": {
"1": "Çok Az",
"2": "Az",
"3": "Bazen",
"4": "Genellikle",
"5": "Her Zaman"
}
}],
"surveyName": "deneme formu",
"createdBy": {
"$oid": "61becc2c230fc12274683b6a"
},
"createdAt": {
"$date": "2022-03-10T02:05:54.039Z"
},
"updatedAt": {
"$date": "2022-03-10T02:05:54.039Z"
},
"__v": 0
}
https://i.stack.imgur.com/xT2Cq.png
Without additional filter just by filtering category with following query I am getting reports that I wanted.
.aggregate([
{ $unwind: '$answers' },
{
$group: {
_id: '$answers.SubCategory',
CalculatedWeight: { $avg: '$answers.CalculatedWeight' },
formId: { $first: formId }
}
},
{ $project: { _id: 1, CalculatedWeight: 1, formId: 1 } },
{ $out: 'results' }
])
results:
{
"CalculatedWeight": 19.35483870967742,
"formId": {
"$oid": "62295d028b6b895048b7da92"
}
}
https://i.stack.imgur.com/HxqBe.png
I successfully added age filtering with following block
const docs = await model
.aggregate([
{
$match: {
$and: [
{ age: { $gt: minAge, $lt: maxAge } },
{ formId: { $eq: formId } }
]
}
},
{ $unwind: '$answers' },
{
$group: {
_id: '$answers.SubCategory',
CalculatedWeight: { $avg: '$answers.CalculatedWeight' },
formId: { $first: formId }
}
},
{ $project: { _id: 1, CalculatedWeight: 1, formId: 1 } },
{ $out: 'results' }
])
But I have 5 filters that are, gender, department, age, working years, and education level. If possible I want to be able to combine them with dynamic querying but I don't want to write 120 if cases for combining them. I can't think a way to programmatically filter and calculate according to filters.
Thank you in advance. The question itself might be unclear, sorry about it. I can elaborate if you point out where it is lacking.
Edit:
Filter types can be seen below. Age and working years will be a period with min and max values. If possible I want to be able to combine all filter types but it will probably statistically unreasonable due to the small pool of entries ~200. Most common combinations for filters are: Gender-Department-Age, Working Years- Department, Education - Gender.
Filters:
gender: String - enum
department: String - enum
age: Number - Min and Max
working years: Number - Min and Max
education: String - enum
For anyone else struggling with the same thing, I figured it out this way.
In my situation since only the match query is subject to change I constructed match queries according to query parameters I am getting with following code.
var array = Object.entries(req.query)
array.forEach(x => {
var z = { [x[0]]: { $eq: x[1] } }
matchTry.$and.push(z)
})
output:
{
"$and": [
{
"department": {
"$eq": "IT"
}
},
{
"gender": {
"$eq": "male"
}
}
]
}
Then use mongoose with it
const docs = await model
.aggregate()
.match(match)
.exec()
res.status(200).json({ data: docs })
Related
I have an aggregation query that returns array of questions with different schema depending on type of document. The documents are in the following format. Some questions may look like this
{
"_id": "ID01",
"Description": "1.Any Question?",
"answer": { "option05": "E. " },
"options": {
"option01": "A.",
"option02": "B.",
"option03": "C.",
"option04": "D.",
"option05": "E."
},
"type": "1",
"score": 10
},
and some questions have the following structure
{
"_id": "ID02",
"Description": "Question Description.",
"emq": {
"emq3": {
"answer": "option12",
"explanation": "",
"question": "1 Any Qstn?"
},
"emq4": {
"answer": "option03",
"explanation": "",
"question": "2 Any Qstn?"
},
"emq5": {
"answer": "option06",
"explanation": "",
"question": "3 Any Qstn?"
}
},
"options": {
"option01": "",
"option02": "",
"option03": "",
"option04": "",
},
"type": "2",
"score": 100
},
I have used the following aggregation pipeline for hiding answers in the questions, (the schema in question is migrated and I cannot change its structure)
Aggregation pipeline
{
$lookup: {
from: 'questions',
let: {
question_id: { $toObjectId: '$qId' },
score: '$score',
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$_id', '$$question_id'],
},
},
},
{
$project: {
answer: {
$cond: {
if: { $eq: ['$type', '1'] },
then: 0,
else: {
$cond: {
if: { $eq: ['$type', '3'] },
then: {
$cond: {
if: {
emq: {
regex: /^emq\d+$/,
},
},
then: 0,
else: 1,
},
},
else: 1,
},
},
},
},
Description: 1,
options: 1,
type: 1,
emq: 1,
score: 1,
},
},
}
It works for questions that are of type 1 but unfortunately I am unable to hide answers for type 2 given the fact that they are not only within 'emq' object but also within nested 'emq3' object with dynamic numbers at the end of string 'emq', I applied regex but somehow I am unable to project answer within emq3 object, is there anyway to achieve the following result that hides answers from both questions like this
{
"_id": "ID01",
"Description": "1.Any Question?",
"options": {
"option01": "A.",
"option02": "B.",
"option03": "C.",
"option04": "D.",
"option05": "E."
},
"type": "1",
"score": 10
},
{
"_id": "ID02",
"Description": "Question Description.",
"emq": {
"emq3": {
"question": "1 Any Qstn?"
},
"emq4": {
"question": "2 Any Qstn?"
},
"emq5": {
"question": "3 Any Qstn?"
}
},
"options": {
"option01": "",
"option02": "",
"option03": "",
"option04": "",
},
"type": "2",
"score": 100
},
You can do the followings in an aggregation pipeline:
convert the object emq into an array of k-v tuple using $objectToArray
$project to remove the field answer and explanation
convert the array emq` back to object
db.collection.aggregate([
{
"$addFields": {
"emq": {
"$objectToArray": "$emq"
}
}
},
{
"$project": {
"emq.v.answer": false,
"emq.v.explanation": false,
"answer": false
}
},
{
"$addFields": {
"emq": {
"$arrayToObject": "$emq"
}
}
},
{
"$addFields": {
emq: {
"$cond": {
"if": {
$eq: [
"$emq",
null
]
},
"then": "$$REMOVE",
"else": "$emq"
}
}
}
}
])
Here is the Mongo playground for your reference.
I'm trying to group my participants array in to such a way that for a single participant I should get all the meeting under that participant in an array.
[
{
"_id": "5fc73e7131e6a20f6c492178",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
{
"_id": "5fc74e1254b8d337b4ae36d2",
"rsvp": "Not Answered",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 1",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
},
{
"_id": "5fc73f1cdfc45d3ca0c84654",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 2",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
}
]
my expected result should be like
[{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
meetings: [{meeting1, meeting2, ...and so on}]
},
{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
},
meetings: [{meeting2, meeting3, ...and so on}]
}
]
I'm kinda stuck for hours to figure it out. I tried the approach by using $group and $unwind but I was getting participants as an array consisting of a single participant(object). and on that I was unable to run $match to match according to the participant's email because participants field was an array.
I tried this
const docs = await Meeting.aggregate([
{ $unwind: '$participants' },
{
$lookup: {
from: 'participants',
localField: 'participants',
foreignField: '_id',
as: 'participants'
}
},
{ $match },
{ $group: { _id: "$participants", meetings: { $push: "$$ROOT" } } },
]);
but this is not matching the expected result which I want it to be.
You can unwind to deconstruct the array and use group to get your desired output
db.collection.aggregate([
{
"$unwind": "$participants"
},
{
$group: {
_id: "$participants._id",
participants: {
$first: "$participants"
},
meetings: {
"$addToSet": "$title"
}
}
}
])
Working Mongo playground
I need to filter some users according to some fixed criteria. I have a user collection and a talent collection. The talent collection holds the reference to a master category collection.
What I need is to filter these users according to the category in the talent collection and some keys from the user collection.
For example I need to search for a user whose gender is 'male' and education 'BTech' and will have talents as a programmer and tester
my user collection is like,
{
"_id": "5f1939239bd35429ac9cd78f",
"isOtpVerified": "false",
"role": "user",
"adminApproved": 1,
"status": 0,
"languages": "Malayalam, Tamil, Telugu, Kannada",
"name": "Test user",
"email": "test#email.com",
"phone": "1234567890",
"otp": "480623",
"uid": 100015,
"bio": "Short description from user",
"dob": "1951-09-07T00:00:00.000Z",
"gender": "Male",
"education": "Btech",
"bodyType": "",
"complexion": "",
"height": "",
"weight": "",
"requests": [],
"location": {
"place": "place",
"state": "state",
"country": "country"
},
"image": {
"avatar": "5f1939239bd35429ac9cd78f_avatar.jpeg",
"fullsize": "5f1939239bd35429ac9cd78f_fullsize.png",
"head_shot": "5f1939239bd35429ac9cd78f_head_shot.jpeg",
"left_profile": "5f1939239bd35429ac9cd78f_left_profile.png",
"right_profile": "5f1939239bd35429ac9cd78f_right_profile.png"
},
"__v": 42,
"createdAt": "2020-07-23T07:15:47.387Z",
"updatedAt": "2020-08-18T18:54:22.272Z",
}
Talent collection
[
{
"_id": "5f38efef179aca47a0089667",
"userId": "5f1939239bd35429ac9cd78f",
"level": "5",
"chars": {
"type": "Fresher",
},
"category": "5f19357b50bcf9158c6be572",
"media": [],
"createdAt": "2020-08-16T08:35:59.692Z",
"updatedAt": "2020-08-16T08:35:59.692Z",
"__v": 0
},
{
"_id": "5f3b7e6f7e322948ace30a2c",
"userId": "5f1939239bd35429ac9cd78f",
"level": "3",
"chars": {
"type": "Fresher",
},
"category": "5f19359250bcf9158c6be573",
"media": [
{
"adminApproved": 0,
"status": 0,
"_id": "5f3c22573065f84a48e04a14",
"file": "id=5f1939239bd35429ac9cd78f&dir=test&img=5f1939239bd35429ac9cd78f_image_undefined.jpeg",
"description": "test",
"fileType": "image",
"caption": "test file"
},
{
"adminApproved": 0,
"status": 0,
"_id": "5f3c2d7a8c7f8336b0bfced2",
"file": "id=5f1939239bd35429ac9cd78f&dir=test&img=5f1939239bd35429ac9cd78f_image_1.jpeg",
"description": "this is a demo poster for testing",
"fileType": "image",
"caption": "A Test Poster"
}
],
"createdAt": "2020-08-18T07:08:31.532Z",
"updatedAt": "2020-08-18T19:35:22.899Z",
"__v": 2
}
]
And the category in the above document is a separate one populated to this. the category collection as,
[
{
"_id": "5f19359250bcf9158c6be573",
"status": true,
"title": "Testing",
"description": "Application tester",
"code": "test",
"characteristics": [],
"createdAt": "2020-07-23T07:00:34.221Z",
"updatedAt": "2020-07-23T07:00:34.221Z",
"__v": 0
},
{
"status": true,
"_id": "5f29829a705b4e648c28bc88",
"title": "Designer",
"description": "UI UX Designer",
"code": "uiux",
"createdAt": "2020-08-04T15:45:30.125Z",
"updatedAt": "2020-08-04T15:45:30.125Z",
"__v": 0
},
{
"_id": "5f19357b50bcf9158c6be572",
"status": true,
"title": "programming",
"description": "Java programmer",
"code": "program",
"createdAt": "2020-07-23T07:00:11.137Z",
"updatedAt": "2020-07-23T07:00:11.137Z",
"__v": 0
}
]
So my filter terms will be;
{
categories: ["5f19359250bcf9158c6be573", "5f19357b50bcf9158c6be572"],
minAge: 18,
maxAge: 25,
minHeight: 5,
maxHeight: 6,
minWeight: 50,
maxWeight: 80,
complexion: "white",
gender: "male",
}
And the expected result will be a user have both the above talents and followed conditions,
{
users: { ..User details.. },
medias: { ...medias from the matching talents.. }
}
If there are two collections you need to join them either by primary key or _id with foriegn fields and you can use $lookup with $match to filter down.
Documentation
You need to use $lookup with pipeline,
$match you condition for category match
$lookup to join users collection
$match conditions for users collections fields
$match exclude documents that don't found matching users of criteria passed in conditions
db.talents.aggregate([
{
$match: {
category: { $in: ["5f19359250bcf9158c6be573", "5f19357b50bcf9158c6be572"] }
}
},
{
$lookup: {
from: "users",
as: "users",
let: { userId: "$userId" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$$userId", "$_id"] },
{ $eq: ["$gender", "Male"] },
{ $eq: ["$education", "Btech"] }
// ... add you other match criteria here
]
}
}
}
]
}
},
{ $match: { users: { $ne: [] } } }
])
Playground
I have 2 entities to merge: Customer and Feedback. Feedback contains an embedded array of upvotes (Upvote)
A customer is not able to upvote more than once for a specific feedback.
What I would like to achieve is - given a specific feedback id - get the complete list of customers with an additional virtual attribute that states whether he/she upvoted the given feedback.
Customer.aggregate(
[
{
$match: { company_id: new ObjectID(req.user.company_id) }
},
{
$lookup: {
from: 'feedbacks',
let: { 'c_id': '$_id' },
pipeline: [
{
$unwind: '$upvotes'
},
{
$match: { $expr: { $eq: ['$upvotes.customer_id._id', '$$c_id'] } }
}
],
as: 'upvotes'
}
}
],
function(err, customers) {
if (err) {
console.log(err);
res.status(400).send(err);
} else {
res.send({ customers });
}
}
);
To do that I have to look through the list of upvotes for that specific feedback and, then, join it with the customer table using the customer_id.
The above mentioned approach does not work. Any suggestion what I am doing wrong?
Sample data (Feedback)
{
"size": 0,
"points": 50,
"status": "open",
"potential": 0,
"real": 5000,
"_id": "5c3d033271ceb7edc37d156c",
"title": "Custom Invoice Templates",
"description": "Provide an editor to create custom invoices.",
"owner_id": {
"_id": "5c3b684f7cec8be977c2a465",
"email": "maurizio#acme.com"
},
"company_id": "5c3b684f7cec8be977c2a462",
"project_id": "5c3b68507cec8be977c2a468",
"upvotes": [
{
"_id": "5c3fa5b371ceb7edc37d159a",
"comments": "bbbb",
"priority": "should",
"customer_id": {
"size": 0,
"potential": 0,
"real": 5000,
"_id": "5c3b68507cec8be977c2a485",
"name": "Oyomia Ltd."
},
"owner_id": {
"_id": "5c3b684f7cec8be977c2a465",
"email": "maurizio#acme.com"
}
}
],
"updatedAt": "2019-01-16T21:44:19.215Z",
"createdAt": "2019-01-14T21:46:26.286Z",
"__v": 0
}
Sample data (Customer)
{
"size": 0,
"potential": 0,
"real": 5000,
"_id": "5c3b68507cec8be977c2a485",
"name": "Oyomia Ltd.",
"contact": {
"_id": "5c40f8de71ceb7edc37d15ab",
"name": "Nick Page",
"email": "np#oyoma.com"
},
"company_id": "5c3b684f7cec8be977c2a462",
"deals": [
{
"value": 5000,
"_id": "5c3b68507cec8be977c2a487",
"name": "Armour batch",
"status": "won",
"type": "non_recurring",
"updatedAt": "2019-01-13T16:33:20.870Z"
}
],
"__v": 0,
"updatedAt": "2019-01-17T21:51:26.877Z"
}
i’m new at Mongo, using v3.2. I have 2 collections Parent & Child. I’d like to use Parent.aggregate and use $lookup to “join” Child then perform $text $search on a field in Child and a date-range seach on the parent. Is this possible...?
In line with the comments already given, it is true that you cannot perform a $text search on the results of a $lookupsince there would not be an available index at any stage other than the very first pipeline stage. And it is true that especially considering that you really want the "join" to occur based on the results from the "child" collection, then it would indeed be better to search on the "child" instead.
Which brings the obvious conclusion that in order to do this you perform the aggregation on the "child" collection with the initial $text query and then $lookup the "parent" instead of the other way around.
As a working example, and just using the core driver for demonstration purposes:
MongoClient.connect('mongodb://localhost/rlookup',function(err,db) {
if (err) throw err;
var Parent = db.collection('parents');
var Child = db.collection('children');
async.series(
[
// Cleanup
function(callback) {
async.each([Parent,Child],function(coll,callback) {
coll.deleteMany({},callback);
},callback);
},
// Create Index
function(callback) {
Child.createIndex({ "text": "text" },callback);
},
// Create Documents
function(callback) {
async.parallel(
[
function(callback) {
Parent.insertMany(
[
{ "_id": 1, "name": "Parent 1" },
{ "_id": 2, "name": "Parent 2" },
{ "_id": 3, "name": "Parent 3" }
],
callback
);
},
function(callback) {
Child.insertMany(
[
{
"_id": 1,
"parent": 1,
"text": "The little dog laughed to see such fun"
},
{
"_id": 2,
"parent": 1,
"text": "The quick brown fox jumped over the lazy dog"
},
{
"_id": 3,
"parent": 1,
"text": "The dish ran away with the spoon"
},
{
"_id": 4,
"parent": 2,
"text": "Miss muffet on here tuffet"
},
{
"_id": 5,
"parent": 3,
"text": "Lady is a fox"
},
{
"_id": 6,
"parent": 3,
"text": "Every dog has it's day"
}
],
callback
)
}
],
callback
);
},
// Aggregate with $text and $lookup
function(callback) {
Child.aggregate(
[
{ "$match": {
"$text": { "$search": "fox dog" }
}},
{ "$project": {
"parent": 1,
"text": 1,
"score": { "$meta": "textScore" }
}},
{ "$sort": { "score": { "$meta": "textScore" } } },
{ "$lookup": {
"from": "parents",
"localField": "parent",
"foreignField": "_id",
"as": "parent"
}},
{ "$unwind": "$parent" },
{ "$group": {
"_id": "$parent._id",
"name": { "$first": "$parent.name" },
"children": {
"$push": {
"_id": "$_id",
"text": "$text",
"score": "$score"
}
},
"score": { "$sum": "$score" }
}},
{ "$sort": { "score": -1 } }
],
function(err,result) {
console.log(JSON.stringify(result,undefined,2));
callback(err);
}
)
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
This results in the $text matches from the query on the Child populated within each Parent, as well as being ordered by "score":
[
{
"_id": 1,
"name": "Parent 1",
"children": [
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog",
"score": 1.1666666666666667
},
{
"_id": 1,
"text": "The little dog laughed to see such fun",
"score": 0.6
}
],
"score": 1.7666666666666666
},
{
"_id": 3,
"name": "Parent 3",
"children": [
{
"_id": 5,
"text": "Lady is a fox",
"score": 0.75
},
{
"_id": 6,
"text": "Every dog has it's day",
"score": 0.6666666666666666
}
],
"score": 1.4166666666666665
}
]
This ultimately makes sense and will be a lot more efficient than querying from the "parent" to find all "children" in a $lookup and then "post filtering" with $match to remove any "children" that did not meet criteria, and then subsequently discarding the "parents" without any match.
The same case is true for mongoose style "referencing" where you included an "array" of "children" within the "parent" instead of recording on the child. So as long as the "localField" on the child ( _id in that case ) is the same type as defined within the array on the parent as "foriegnField" ( which is will be if it was working with .populate() anyway ) then you are still getting the matched "parent(s)" for each "child" in the $lookup result.
This all comes down to reversing your thinking and realizing that the $text results are the most important thing, and therefore "that" is the collection on which the operation needs to be initiated.
It's possible, but just do it the other way around.
Using mongoose style with list of referenced children in the parent
Just showing the reverse case for references on the parent as well as date filtering:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/rlookup');
var parentSchema = new Schema({
"_id": Number,
"name": String,
"date": Date,
"children": [{ "type": Number, "ref": "Child" }]
});
var childSchema = new Schema({
"_id": Number,
"text": { "type": String, "index": "text" }
},{ "autoIndex": false });
var Parent = mongoose.model("Parent",parentSchema),
Child = mongoose.model("Child",childSchema);
async.series(
[
function(callback) {
async.each([Parent,Child],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
Child.ensureIndexes({ "background": false },callback);
},
function(callback) {
async.parallel(
[
function(callback) {
Parent.create([
{
"_id": 1,
"name": "Parent 1",
"date": new Date("2016-02-01"),
"children": [1,2]
},
{
"_id": 2,
"name": "Parent 2",
"date": new Date("2016-02-02"),
"children": [3,4]
},
{
"_id": 3,
"name": "Parent 3",
"date": new Date("2016-02-03"),
"children": [5,6]
},
{
"_id": 4,
"name": "Parent 4",
"date": new Date("2016-01-15"),
"children": [1,2,6]
}
],callback)
},
function(callback) {
Child.create([
{
"_id": 1,
"text": "The little dog laughed to see such fun"
},
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog"
},
{
"_id": 3,
"text": "The dish ran awy with the spoon"
},
{
"_id": 4,
"text": "Miss muffet on her tuffet"
},
{
"_id": 5,
"text": "Lady is a fox"
},
{
"_id": 6,
"text": "Every dog has it's day"
}
],callback);
}
],
callback
);
},
function(callback) {
Child.aggregate(
[
{ "$match": {
"$text": { "$search": "fox dog" }
}},
{ "$project": {
"text": 1,
"score": { "$meta": "textScore" }
}},
{ "$sort": { "score": { "$meta": "textScore" } } },
{ "$lookup": {
"from": "parents",
"localField": "_id",
"foreignField": "children",
"as": "parent"
}},
{ "$project": {
"text": 1,
"score": 1,
"parent": {
"$filter": {
"input": "$parent",
"as": "parent",
"cond": {
"$and": [
{ "$gte": [ "$$parent.date", new Date("2016-02-01") ] },
{ "$lt": [ "$$parent.date", new Date("2016-03-01") ] }
]
}
}
}
}},
{ "$unwind": "$parent" },
{ "$group": {
"_id": "$parent._id",
"name": { "$first": "$parent.name" },
"date": { "$first": "$parent.date" },
"children": {
"$push": {
"_id": "$_id",
"text": "$text",
"score": "$score"
}
},
"score": { "$sum": "$score" }
}},
{ "$sort": { "score": -1 } }
],
function(err,result) {
console.log(JSON.stringify(result,undefined,2));
callback(err);
}
)
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
With the output:
[
{
"_id": 1,
"name": "Parent 1",
"date": "2016-02-01T00:00:00.000Z",
"children": [
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog",
"score": 1.1666666666666667
},
{
"_id": 1,
"text": "The little dog laughed to see such fun",
"score": 0.6
}
],
"score": 1.7666666666666666
},
{
"_id": 3,
"name": "Parent 3",
"date": "2016-02-03T00:00:00.000Z",
"children": [
{
"_id": 5,
"text": "Lady is a fox",
"score": 0.75
},
{
"_id": 6,
"text": "Every dog has it's day",
"score": 0.6666666666666666
}
],
"score": 1.4166666666666665
}
]
Noting that the "Parent 4" which would otherwise of had the largest ranking is removed since the date does not fall in the query range applied with $filter.