简体   繁体   中英

How to get an extra count field with Django ORM?

My Django Models are like this:

class User(models.Model):
    username = models.CharField(max_length=32)
class Message(models.Model):
    content = models.TextField()
class UserMessageRel(models.Model):
    user = models.ForeignKey(User)
    message = models.ForeignKey(Message)
    is_read = models.BooleanField()

Now I want to get all messages, for each message, I need to know how many users that received it has read it.

The naive way to do it is:

msgs = Message.objects.all()
messages = []
for msg in msgs:
    reads = UserMessageRel.objects.filter(message=msg, is_read=True).count()
    messages.append((msg, reads))

But this is very inefficient, with a SQL query to get the number of reads for each message.

I am not sure if this can be done with annotations or aggregations in ORM?

What I want is something like this:

msgs_with_reads = Message.objects.all().annotate(
    number_of_reads=Count("user_message_rel_with_is_read_true"))

which can be translated into one nice SQL query.

Is this achievable?

I'm interpreting your question to be that you want to improve query time for this count. Unfortunately, with the current setup, a full table scan is necessary. There are ways to improve it, the easiest being indexing. You could add an index on the Message id column in UserMessageRel, which would speed up the read time (at the cost of space, of course). The most readable way to access this count though, is Pieter's answer.

You can do a related lookup from the Message object, I would put a helper function on the Message model like this, then you would be able to call the function from the object.

def get_read_count(self):
    return self.usermessagerel_set.filter(is_read=True).count()

message_obj.get_read_count()

I didn't find a way to use Django ORM to generate one SQL query for my requirement, but the following code can generate 2 queries:

messages = Message.objects.all()
messageReads = UserMessageRel.objects.filter(isRead=True).
    values("message_id").annotate(cnt=Count("user"))

Then I can map the messages with their read count in python. This solution is good enough for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM