tester
tester

Reputation: 23159

Nested document value search in MongoDB

Given the following user object in MongoDB, what would be the fastest way to search by relation.$.status == 'friends'?

Need to find a way to find in a user collection's relation data that contains an object with a value of "friends" (see pseudo query below).

The keys for this particular collection have been set to uids, so assume that the "User Data" section below may not be altered.

User Data:

{
    "_id": ObjectId('1a1a1a1a111a1a11a1a1a1a'),
    "name": "Tester",
    "relation": {
        "2b2b2b2b2b2b2b2b2b2b2b": {
            "status": 'friends',
            "status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
            // etc...
        },
        "3c3c3c3c3c3c3c3c3c3c3c": {
            "status": 'engaged',
            "status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
            // etc...
        }
    }
}

Query:

var query = {
    // pseudo, key-ignoring nested document search
    'relation.$.status': 'friends'
}
var projection = {
    'relation': 1
}
db.user.findOne(query, projection)

Desired Output:

{ 
    "2b2b2b2b2b2b2b2b2b2b2b": {
       "status": 'friends',
       "status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
       // etc...
    }
}

The obvious correction to the User Data structure should be making relation an array of objects of which contain _id and status as keys. Kinda looking for a temporary shim here.

Upvotes: 2

Views: 1922

Answers (1)

Shad
Shad

Reputation: 4464

You can apply mongodb Map/Reduce technique for this task.

1) map function will determine nested objects from relation object, which contain status field equal to "friends" value:

var map = function () {
    for (var key in this.relation) {
        var relation = this.relation[key];
        if (relation.status == "friends")
            emit(key, relation);
    }
}

2) reduce function will actually do nothing, just return single emitted relation:

var reduce = function (key, values) {
    return values[0];
}

As a result you'll have collection with such objects:

{
  _id: "2b2b2b2b2b2b2b2b2b2b2b",
  value: {
    status: "friends",
    status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
    // etc...
  }
}

If you decide to replace relation object with array, you can use aggregation framework to construct similar query.

User Data:

{
    _id: ObjectId("1a1a1a1a111a1a11a1a1a1a"),
    name: "Tester",
    relations: [
        {
            id: "2b2b2b2b2b2b2b2b2b2b2b",
            status: "friends",
            status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
            // etc...
        },
        {
            id: "3c3c3c3c3c3c3c3c3c3c3c",
            status: "engaged",
            status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
            // etc...
        }
    ]
}

Query:

db.collection.aggregate([
    { $unwind: "$relations" },
    { $match: { "relations.status": "friends" } },
    { $project: _id: 0,
        id: "$relations.id",
        status: "$relations.status",
        status_changed_date: "$relations.status_chaged_date"
        // etc...
    }
])

Output:

{
  id: "2b2b2b2b2b2b2b2b2b2b2b",
  status: "friends",
  status_changed_date: ISODate("2013-10-28T00:00:00.0000Z"),
  // etc...
}

Upvotes: 1

Related Questions