1

I have documents with a schema such as follows:

{
    "user_id": 123,
    "services":[
         {"name": "test",
          "data": ...
         },
         {"name": "test1",
          "data": ...
         },
         {"name": "test2",
          "data": ...
         }
    ]
}

I'm trying to get a service by name for a specific user_id returned as follows:

{"name": "test2",
    "data": ...
}

I'm having difficulty wrapping my head around how to do this and seems an aggregation shouldn't be needed for something as simple as this but maybe I'm wrong. I'm sure a projection would work in a find_one statement but I'm not sure what to use. I'm using Motor btw not sure if that helps.


I have tried:

async def get_service_by_name(user_id, name):
    return await db.guilds.find_one({
        'user_id': 123,
        'services': {'$elemMatch': {'name': "test"}}},
        {'user_id: 0, 'services.$': 1}))

But this returns:

{"services":[{"name" : "test", "data" : "blah" }]}

And that's fine as it's close to what I want and all I'd need to do is:

service = await get_service_by_name(123, "test")
service = service['service'][0]

But is there a way to get the data back as just the service without an aggregation? And if not, then what should the aggregation look like?

Edit

I came up with an aggregation that does this but want to make sure there's no better way:

await db.guilds.aggregate([
    {'$unwind': '$services'},
    {'$match':{
        '_id': 123,
        'services.name': "test"}},
    {'$project': {
        '_id': 0,
        'name': '$services.name',
        'data': '$services.data'}}
])

1 Answer 1

3

You need to run $unwind to get single document from services and $replaceRoot to promote it to root level:

db.guilds.aggregate([
    {
        $match: { user_id: 123, "services.name": "test" }
    },
    {
        $unwind: "$services"
    },
    {
        $match: { "services.name": "test" }
    },
    {
        $replaceRoot: { newRoot: "$services" }
    }
])
Sign up to request clarification or add additional context in comments.

6 Comments

Oh wow, I came up with my own as well, see my edit. I like this way better. Although as far as performance which between yours and mine is better? (I'm leaning toward yours although doesn't hurt to ask)
Also, can the 2 match statements be combined in yours so it's one like I did? I'm not crazy good with aggregations
The sooner you filter out all the documents the better. So after first $match there should be only one document while in your aggregation you $unwind all of them and then run filtering - that should make the difference in terms of performance
Ahh, great I see. Thanks!
@Fanpark I assumed it's unique but added to the answer anyway, thank you !
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.