Your current data structure is a prime candidate for using the Attribute Pattern. That makes it easier to query and makes it possible to index. Otherwise, the fields donāt have known/predictable names.
For example, your document can be transformed from:
{
"03": {
"17": {
"id": "xxx",
"name": "myname",
"lastSeen": "2025-03-17",
"someotherkey": "value"
},
"18": {
"id": "xxx",
"name": "myname",
"lastSeen": "2025-03-18",
"someotherkey": "value"
}
}
}
to:
{
"month": "03",
"days": [
{
"day": "17",
"id": "xxx",
"name": "myname",
"lastSeen": "2025-03-17",
"someotherkey": "value"
},
{
"day": "18",
"id": "xxx",
"name": "myname",
"lastSeen": "2025-03-18",
"someotherkey": "value"
}
]
}
Then a search for ānameā could be simply:
db.collection.find({ "days.name": { $regex: "yna" } })
Mongo Playground 1A
or
db.collection.find({ "days.name": { $regex: "her" } })
Mongo Playground 1B
Some assumptions, questions, and suggestions:
- Does each document represent just one month? If there can be more than one month in a doc, then the example above will need to be nested under a
months array field.
- When you search the name in subdocuments, do you want the whole document or just the matching subdocument in the array?
- If your document has
month and day, it should probably also have year. Or directly use a datetime field with the hour-minute-second set to zero. Like ISODate("2025-03-17T00:00:00Z").
- And then it would be appropriate as a Time Series collection (optional).
Without using the Attribute Pattern and the changes above, you would need to use $objectToArray multiple times so that each field has a known/predictable name. And since no index would exist* (see below), it canāt be optimised and has to execute on every document until the $match stage after the transformations:
db.collection.aggregate([
{
$replaceWith: {
_id: "$_id",
doc: {
// assumes one month per doc (one top-level field)
$first: {
$filter: {
input: { $objectToArray: "$$ROOT" },
cond: {
// skip _id since it's separate
$ne: ["$$this.k", "_id"]
}
}
}
}
}
},
{
$set: {
doc: {
v: { $objectToArray: "$doc.v" }
}
}
},
{
$match: {
// SEARCH TERM HERE
"doc.v.v.name": { $regex: "yna" }
}
},
{
// undo the transformations
$set: {
doc: [
{
k: "$doc.k",
v: { $arrayToObject: "$doc.v" }
}
]
}
},
{
$replaceWith: {
$mergeObjects: [
{ _id: "$_id" },
{ $arrayToObject: "$doc" }
]
}
}
])
Mongo Playground 2A (and Mongo Playground 2B)
* You could have a Wildcard Index but the query would need to specify the field names (month and day).