Reputation: 1072
How can I query a MongoDB collection to find documents with a structure as below? The documents have a field called thing
which is a subdocument, and the keys for this field are a form of ID number which will generally not be known by the person writing the query (making dot notation difficult and I assume impossible).
{
"_id" : 3,
"_id2" : 234,
"thing":
{
"2340945683":
{"attribute1": "typeA",
"attribute2": "typeB",
"attribute3": "typeA"
},
"349687346":
{"attribute1": "typeC",
"attribute2": "typeB",
"attribute3": "typeA"
}
},
"username": "user1"
}
Say I want to set a filter which will return the document only if some one or more of the fields within thing
have the condition "attribute1" : "typeC"
?
I need something like
db.collection.find( {thing.ANY_FIELD: $elemMatch:{"attribute1":"typeC"}})
Upvotes: 4
Views: 1816
Reputation: 116
My solution to this was to use two aggregate operations, the first one is called objectToArray and it's purpose is to convert a object into a list of objects with keys and values (see the documentation examples), and the reduce to search in this array of key-values, at the end we end up with a boolean "hasAttribute" indicating that the one field matched the value wee are looking for.
Here is the solution:
db.getCollection("thing").aggregate([
{
$addFields: {
hasAttribute: {
$reduce: {
input: {
$objectToArray: "$thing"
},
initialValue: false,
in: {$or: ["$$value", {$eq: ["typeC", "$$this.v.attribute1"]}]}
}
}
}
},
{
$match: {
hasAttribute: true
}
}
])
Here is the sample output and how the boolean value behaves:
{
"_id" : ObjectId("5ddd63c02e5c579c5076c76f"),
"thing" : {
"349687346" : {
"attribute1" : "typeC",
"attribute2" : "typeB",
"attribute3" : "typeA"
},
"2340945683" : {
"attribute1" : "typeA",
"attribute2" : "typeB",
"attribute3" : "typeA"
}
},
"hasAttribute" : true
}
// ----------------------------------------------
{
"_id" : ObjectId("5ddd63d12e5c579c5076c770"),
"thing" : {
"2340945683" : {
"attribute1" : "typeA",
"attribute2" : "typeB",
"attribute3" : "typeA"
}
},
"hasAttribute" : false
}
// ----------------------------------------------
{
"_id" : ObjectId("5ddd63d12e5c579c5076c771"),
"thing" : {
"349687346" : {
"attribute1" : "typeC",
"attribute2" : "typeB",
"attribute3" : "typeA"
}
},
"hasAttribute" : true
}
Ask for clarifications if you need!
Upvotes: 1
Reputation: 49995
You need to start with $objectToArray to read your keys dynamically. Then you can $map properties along with $anyElementTrue to detect if there's any nested field in thing
containing {"attribute1":"typeC"}
:
db.collection.aggregate([
{
$match: {
$expr: {
$anyElementTrue: {
$map: {
input: { $objectToArray: "$thing" },
in: { $eq: [ "$$this.v.attribute1", "typeC" ] }
}
}
}
}
}
])
Upvotes: 3