0

For example, I'm having a document like this:

{
   name: "Test",
   params: {
     device: "windows",
     gender: "m",
     age: 28
   }
}

Now I get the following params input:

{
   device: "windows",
   gender: "m"
}

In this case, I want to find all documents that partially match with the input object. Is there an easy way to solve this issue? I've tried $elemMatch but this seems only to work with arrays.

user2891491
  • 351
  • 8
  • 16

3 Answers3

1

If I understood your question correctly, you have an input object which may contain some of the fields in the main document's params object, in any order, for example:

{
   device: "windows",
   gender: "m"
}

{
   gender: "m",
   device: "windows"
}

{
   device: "windows",
   age: 28
}

and you want to match only if all the fields in the input object exist in the main document:

{
   device: "linux",    // NO MATCH
   gender: "m"
}

{
   gender: "m",        // MATCH
   device: "windows"
}

{
   device: "windows",  // NO MATCH
   age: 29
}

Correct?


Option 1

Does your param object always contain only those three fields (device, gender and age)?

If so, you can manually project each of them at the root level, do your match, and "unproject" them again:

db.my_collection.aggregate([
    {
        $project: {
            name: 1, 
            params: 1, 
            device: "$params.device",  // add these three for your match stage
            gender: "$params.gender", 
            age: "$params.age"
        }
    },
    {
        $match: input_params
    },
    {
        $project: {name: 1, params: 1}  // back to original
    }
]);

But I assume you want to do this for any number of fields inside your params object.


Option 2

Do you have a way to manipulate the input object coming in? If so, you could preface all fields with "params.":

let input_params = 
{
   device: "windows",
   gender: "m"
};

let new_input_params =
{
   "params.device": "windows",
   "params.gender": "m"
};

Then your query would just be:

db.my_collection.find(new_input_params);

Option 3

If you have no way of modifying the input, you can use the $replaceRoot aggregation operator (as of version 3.4, credit to this answer) to flatten your params into the root document. Since this will replace the root document with the embedded one, you need to extract the fields you are interested in first, at least the _id field:

db.my_collection.aggregate([
    {
        $addFields: {"params.id": "$_id"}  // save _id inside the params doc, 
                                           // as you will lose the original one
    },
    {
        $replaceRoot: {newRoot: "$params"}
    },
    {
        $match: input_params
    },
    ...
]);

This will match the documents and retain the _id field which you can use to get the rest of the document again, through a $lookup for example:

    ...
    {
        $lookup: {
            from: "my_collection", 
            localField: "id", 
            foreignField: "_id", 
            as: "doc"
        }
    },
    ...

This will get your documents in the following format:

{ 
  "device" : "windows", 
  "gender" : "m", 
  "age" : 28, 
  "id" : ObjectId("XXX"), 
  "doc" : [ 
    {
      "_id" : ObjectId("XXX"), 
      "name" : "Test", 
      "params" : { 
        "device" : "windows", 
        "gender" : "m", 
        "age" : 28 
      }
    }
  ]
}

To go full circle and get your original document format back, you can:

    ...
    {
        $unwind: "$doc" // get rid of the [] around the "doc" object 
                        // ($lookup always results in array)
    },
    {
        $replaceRoot: {newRoot: "$doc"} // get your "doc" back to the root
    }
    ...

As I write this, I cannot believe there is no cleaner way of doing this using MongoDB alone, but I cannot think of any.

I hope this helps!

t-brito
  • 76
  • 7
0

You can use $or operator for partial comparison of the fields from params object. You can use like this:

db.collection.find({
  $or: [
    {
      "params.device": "windows"
    },
    {
      "params.gender": "m"
    }
  ]
})
0

Using projection

A document may have many fields, but not all of these fields may be necessary and important when requested. In this case, you can only include the required fields in the sample by using the projection.

By default, queries in MongoDB return all fields in matching documents. To limit the amount of data that MongoDB sends to applications, you can include a projection document to specify or restrict fields to return.

For example, you previously added the following documents to the database:

> db.users.insertOne({"name": "Jennifer", "gender": "female", languages: ["english", "spanish"]})
> db.users.insertOne({"name": "Alex", "gender": "male", languages: ["english", "french"]})
> db.users.insertOne({"name": "Maria", "gender": "female", languages: ["english", "german"]})

And now you want to display information that match the field gender and has value female, so you can specify it via query:

db.users.find({gender: "female"}, {name: 1})

The operation returns the following documents:

{ "name" : "Jennifer", "gender" : "female" }
{ "name" : "Maria", "gender" : "female" }

The <value> can be any of the following:

{ field1: <value>, field2: <value> ... }
  • 1 or true to include the field in the return documents.
  • 0 or false to exclude the field.
  • Expression using a Projection Operators.

Moreover, if you don't want to concretize the selection, but want to display all the documents, then we can leave the first brackets empty:

db.users.find({}, {name: 1, _id: 0})
invzbl3
  • 5,872
  • 9
  • 36
  • 76