John F
John F

Reputation: 1072

MongoDB - Find documents matching certain condition for unknown field keys

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

Answers (2)

Ali Husseinat
Ali Husseinat

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

mickl
mickl

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" ] }                         
                    }
                }
            }
        }
    }
])

Mongo Playground

Upvotes: 3

Related Questions