1

I have three Collections:

UserCollection, DepartmentCollection and QuestionsCollection

QuestionsCollection is a configurable list of questions.

Users and Departments each answer a survey generated from QuestionsCollection and as a result each have an embedded array of questionAnswer objects with each object having a questionid and an answer property.

An example User document from the UserCollection would be:

        {
            id:SoEtnNvN8B3QYcd3N,
            questionAnswers:[
            {
               questionId: 1, 
               answer: "melbourne"
            }, 
            {
               questionId: 2,
               answer: "10"}]
            }
        }

A department would have the same embedded array but could contain more or less questionAnswer objects. With different or same answers of course.

The problem:

What i'd like to do is be able to get a single Users questionAnswers array as the search criteria, and see how many departments match those questionAnswers.

The matching would match on both properties, and a user must answer the same questions a department has answered in order to match e.g.

  • If the department has answered more questions than the user it's automatically not a match.
  • If the user and department have answered the same number of questions then all questionAnswer values should match.
  • If a user has answered more questions than a department, then all the departments questionAnswers must be contained within the users questionAnswers.

The answers would be exact matches, though I might need a mechanism for ranged searches at some point.

I've looked into maybe building a query using the users questionAnswers array and using $elemMatch to search on the DepartmentCollection, but I'm not sure how to implement this. Any help or direction would be appreciated.

1 Answer 1

1

This is one of those cases where you are going to have to "build" your query in code based upon the data from the current user you are processing.

In order to match according to your criteria, the query must be made to match the logic for a number of conditions. The array form from the user data is useful, but not an exact fit into the actual required query parts. So it needs some transformation, and working from your example data you would form it like this:

// Actually found by querying for the required user
var user =  {
   "id": "SoEtnNvN8B3QYcd3N",
   "questionAnswers":[
       {
           "questionId": 1, 
           "answer": "melbourne"
        }, 
        {
           "questionId": 2,
           "answer": "10"
        }
   ]
};

// Basic query
var query = {
    "$or": [
        { "$and": [ ] },
        { 
            "questionAnswers": { "$size": 1 },
            "$or": []
        }
    ]
};

// add the elements
user.questionAnswers.forEach(function(answer) {
    query.$or[0].$and.push({ "questionAnswers": { "$elemMatch" : answer }});
    query.$or[1].$or.push({"questionAnswers": { "$elemMatch":  answer }});
});


Department.find(query);

On your data for "user" the query will construct like this:

{
    "$or" : [
        {
            "$and" : [
                {
                    "questionAnswers" : { "$elemMatch" : {
                        "questionId" : 1,
                        "answer" : "melbourne"
                    }}
                },
                {
                    "questionAnswers" : { "$elemMatch" : {
                        "questionId" : 2,
                        "answer" : "10"
                    }}
                }
            ]
        },
        {
            "questionAnswers" : {
                "$size" : 1
            },
            "$or" : [
                {
                    "questionAnswers" : { "$elemMatch" : {
                        "questionId" : 1,
                        "answer" : "melbourne"
                    }}
                },
                {
                    "questionAnswers" : { "$eleMatch" : {
                        "questionId" : 2,
                        "answer" : "10"
                    }}
                }
            ]
        }
    ]
}

The whole statement is wrapped by $or for each of your conditions. In the first one the documents must "all match" the conditions set inside the $and statement, which is an array of query conditions.

In the second element to the top $or is essentially an $and condition (in the natural implicit way since keys don't collide) that first considers the $size of the array according to your rules, so only arrays with a single element. The paired condition is within another $or to see if that single element matches "any" of the conditions derived from the "questionAnswers" provided by the user. So it that single element matches any one of the user elements then it is a match.

Finally $elemMatch gives you a bit of flexibility in the design for where the target array documents may either contain more properties than are in the source data or even where the property keys are not in the same order. Without that operator the sub-documents of the array would be required to be a "exact match" of the source.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the comprehensive reply. I think I'm almost there, though I've updated the conditions in my first post as I don't think I was clear enough. My issue at the moment is the third point, where a user has answered more questions than a department and the departments questionAnswer array must be contained within the users questionAnswer array. Would I have to do a find within my UserCollection for just that special case?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.