Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongo Embedded Document Query

I've 2 DynamicDocuments:

class Tasks(db.DynamicDocument):
    task_id = db.UUIDField(primary_key=True,default=uuid.uuid4)
    name = db.StringField()
    flag = db.IntField()

class UserTasks(db.DynamicDocument):
    user_id = db.ReferenceField('User')
    tasks = db.ListField(db.ReferenceField('Tasks'),default=list)

I want to filter the UserTasks document by checking whether the flag value (from Tasks Document) of the given task_id is 0 or 1, given the task_id and user_id. So I query in the following way:-

obj = UserTasks.objects.get(user_id=user_id,tasks=task_id)

This fetches me an UserTask object.

Now I loop around the task list and first I get the equivalent task and then check its flag value in the following manner.

task_list = obj.tasks
for t in task_list:
    if t['task_id'] == task_id:
        print t['flag']

Is there any better/direct way of querying UserTasks Document in order to fetch the flag value of Tasks Document.

PS : I could have directly fetched flag value from the Tasks Document, but I also need to check whether the task is associated with the user or not. Hence I directly queried the USerTasks document.

like image 974
PythonEnthusiast Avatar asked Sep 26 '22 15:09

PythonEnthusiast


1 Answers

Can we directly filter on a document with the ReferenceField's fields in a single query?

No, its not possible to directly filter a document with the fields of ReferenceField as doing this would require joins and mongodb does not support joins.

As per MongoDB docs on database references:

MongoDB does not support joins. In MongoDB some data is denormalized, or stored with related data in documents to remove the need for joins.

From another page on the official site:

If we were using a relational database, we could perform a join on users and stores, and get all our objects in a single query. But MongoDB does not support joins and so, at times, requires bit of denormalization.

Relational purists may be feeling uneasy already, as if we were violating some universal law. But let’s bear in mind that MongoDB collections are not equivalent to relational tables; each serves a unique design objective. A normalized table provides an atomic, isolated chunk of data. A document, however, more closely represents an object as a whole.

So in 1 query, we can't both filter tasks with a particular flag value and with the given user_id and task_id on the UserTasks model.

How to perform the filtering then?

To perform the filtering as per the required conditions, we will need to perform 2 queries.

In the first query we will try to filter the Tasks model with the given task_id and flag. Then, in the 2nd query, we will filter UserTasks model with the given user_id and the task retrieved from the first query.

Example:

Lets say we have a user_id, task_id and we need to check if the related task has flag value as 0.

1st Query

We will first retrive the my_task with the given task_id and flag as 0.

my_task = Tasks.objects.get(task_id=task_id, flag=0) # 1st query

2nd Query

Then in the 2nd query, you need to filter on UserTask model with the given user_id and my_task object.

my_user_task = UserTasks.objects.get(user_id=user_id, tasks=my_task) # 2nd query

You should perform 2nd query only if you get a my_task object with the given task_id and flag value. Also, you will need to add error handling in case there are no matched objects.

What if we have used EmbeddedDocument for the Tasks model?

Lets say we have defined our Tasks document as an EmbeddedDocument and the tasks field in UserTasks model as an EmbeddedDocumentField, then to do the desired filtering we could have done something like below:

my_user_task = UserTasks.objects.get(user_id=user_id, tasks__task_id=task_id, tasks__flag=0)

Getting the particular my_task from the list of tasks

The above query will return a UserTask document which will contain all the tasks. We will then need to perform some sort of iteration to get the desired task.

For doing that, we can perform list comprehension using enumerate(). Then the desired index will be the 1st element of the 1-element list returned.

my_task_index = [i for i,v in enumerate(my_user_task.tasks) if v.flag==0][0]
like image 65
Rahul Gupta Avatar answered Sep 30 '22 06:09

Rahul Gupta