问题
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.
回答1:
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]
回答2:
@Praful, based on your schema you need two queries because mongodb does not have joins, so if you want to get "all the data" in one query you need a schema which fit that case. ReferenceField is a special field which does a lazy load of the other collection (it requires a query).
Based on the query you need, I recommend you to change your schema to fit that. The idea behind NOSQL engines is "denormalization" so it is not bad to have a list of EmbeddedDocument. EmbeddedDocument can be a smaller document (denormalized version) with a set of fields instead of all of them.
If you do not want to load the whole document into memory while querying you can exclude that fields using a "projection". Supossing your UserTasks has a list of EmbeddedDocument with the task you could do:
UserTasks.objects.exclude('tasks').filter(**filters)
I hope it helps you.
Good luck!
来源:https://stackoverflow.com/questions/32746083/mongo-embedded-document-query