Include Specific Fields in a Wildcard Index in MongoDB

When you create a wildcard index in MongoDB, you have the option of specifying a single field, all fields, or just some.

You can use the wildcardProjection parameter to include or exclude specific field paths from the wildcard index. This article presents an example of including specific fields in the wildcard index.

Example Document

Suppose we have a collection called pets with the following documents:

{
	"_id" : 1,
	"name" : "Wag",
	"details" : {
		"type" : "Dog",
		"weight" : 20,
		"awards" : {
			"Florida Dog Awards" : "Top Dog",
			"New York Marathon" : "Fastest Dog",
			"Sumo 2020" : "Biggest Dog"
		}
	}
}
{
	"_id" : 2,
	"name" : "Fetch",
	"details" : {
		"born" : ISODate("2020-06-22T14:00:00Z"),
		"color" : "Black"
	}
}
{
	"_id" : 3,
	"name" : "Scratch",
	"details" : {
		"eats" : [
			"Mouse Porridge",
			"Bird Soup",
			"Caviar"
		],
		"type" : "Cat",
		"born" : ISODate("2020-12-19T14:00:00Z")
	}
}

We could create a wildcard index on the whole collection, but only include the fields we want.

Create the Index

Here’s an example:

db.pets.createIndex(
  { "$**" : 1 },
  {
    "wildcardProjection" : {
      "details.type" : 1,
      "details.born" : 1
    }
  }
)

Output:

{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}

The { "$**" : 1 } part is what creates the wildcard index, and the wildcardProjection part is the part that specifies which fields to include. In this case we’ve included the details.type field and the details.born field. Giving them a value of 1 explicitly includes them in the index.

View the Index

We can view the indexes on the collection by calling the getIndexes() method:

db.pets.getIndexes()

Result:

[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"$**" : 1
		},
		"name" : "$**_1",
		"wildcardProjection" : {
			"details.type" : 1,
			"details.born" : 1
		}
	}
]

We can see that there are two indexes.

  • The first index is on the _id field. This was created when the collection was created (MongoDB creates a unique index on the _id field during the creation of a collection).
  • The second index is our wildcard index. We can see that it’s been automatically named $**_1, and it includes the fields that we specified.

Test the Index

We can also run some queries to see whether our index will be used or not.

In theory, the following query should use the index:

db.pets.find( { "details.type" : "Dog" } )

To test this, we can append the explain() method to view the query plan:

db.pets.find( { "details.type" : "Dog" } ).explain()

Result:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "PetHotel.pets",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"details.type" : {
				"$eq" : "Dog"
			}
		},
		"queryHash" : "F1C5286F",
		"planCacheKey" : "5326DE93",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"$_path" : 1,
					"details.type" : 1
				},
				"indexName" : "$**_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"$_path" : [ ],
					"details.type" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"$_path" : [
						"[\"details.type\", \"details.type\"]"
					],
					"details.type" : [
						"[\"Dog\", \"Dog\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

We can see that it used an index scan (IXSCAN) on our index.

In contrast to this, here’s what happens when we run a query on a field that’s not included in the index:

db.pets.find( { "details.awards.New York Marathon" : "Fastest Dog" } ).explain()

Result:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "PetHotel.pets",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"details.awards.New York Marathon" : {
				"$eq" : "Fastest Dog"
			}
		},
		"queryHash" : "EC0D5185",
		"planCacheKey" : "EC0D5185",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"details.awards.New York Marathon" : {
					"$eq" : "Fastest Dog"
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

In this case it did a collection scan (COLLSCAN), so as expected, it didn’t use the index.