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!