mongodb

mongodb-query

wildcard

Is it possible to wildcard the key in a query? For instance, given the following record, I'd like to do a .find({'a.*': 4})

This was discussed here https://jira.mongodb.org/browse/SERVER-267 but it looks like it's not been resolved.

{
  'a': {
    'b': [1, 2],
    'c': [3, 4]
  }
}

Solution 1

As asked, this is not possible. The server issue you linked to is still under "issues we're not sure of".

MongoDB has some intelligence surrounding the use of arrays, and I think that's part of the complexity surrounding such a feature.

Take the following query db.foo.find({ 'a.b' : 4 } ). This query will match the following documents.

{ a: { b: 4 } }
{ a: [ { b: 4 } ] }

So what does "wildcard" do here? db.foo.find( { a.* : 4 } ) Does it match the first document? What about the second?

Moreover, what does this mean semantically? As you've described, the query is effectively "find documents where any field in that document has a value of 4". That's a little unusual.

Is there a specific semantic that you're trying to achieve? Maybe a change in the document structure will get you the query you want.

Solution 2

Starting from MongoDB v3.4+, you can use $objectToArray to convert a into an array of k-v tuples for querying.

db.collection.aggregate([
  {
    "$addFields": {
      "a": {
        "$objectToArray": "$a"
      }
    }
  },
  {
    $match: {
      "a.v": 4
    }
  },
  {
    "$addFields": {
      // cosmetics to revert back to original structure
      "a": {
        "$arrayToObject": "$a"
      }
    }
  }
])

Here is the Mongo playground for your reference.

Solution 3

I've came across this question because I faced the same issue. The accepted answer provider here does explains why this is not supported but not really solves the issue itself.

I've ended up with a solution that makes the wildcard usage purposed here redundant and share here just in case someone will find this post some day

Why I wanted to use wildcards in my MongoDB queries? In my case, I needed this "feature" in order to be able to find a match inside a dictionary (just as the question's code demonstrates).

What's the alternatives? Use a reversed map (very similar to how DNS works) and simply use it. So, in our case we can use something similar to this:

{
  "a": {
    "map": {
      "b": [1, 2, 3],
      "c": [3, 4]
    },
    "reverse-map": {
      "1": [ "b" ],
      "2": [ "b" ],
      "3": [ "b", "c" ],
      "4": [ "c" ]
    }
  }
}

I know, it takes more memory and insert / update operations should validate this set is always symmetric and yet - it solves the problem. Now, instead of making an imaginary query like

db.foo.find( { a.map.* : 4 } )

I can make an actual query

db.foo.find( { a.reverse-map.4 : {$exists: true} } )

Which will return all items that have a specific value (in our example 4)

I know - this approach takes more memory and you need to manage indexes properly if you want to gain good performance (read the docs) and still - it's good for my use-case. Hope this helps someone else someday as well