Django, How to make multiple annotate in a single queryset

Jean-Michel Provencher picture Jean-Michel Provencher · Jun 17, 2015 · Viewed 7.7k times · Source

I am currently trying to annotate two different number of likes to a User model in Django.

Here's the code I'm using to return the desired querySet

def get_top_user(self):
    return User.objects. \
        annotate(guide_like=Count('guidelike')).\
        annotate(news_like=Count('newslike')).\
        values_list('first_name', 'last_name', 'guide_like','news_like').\
        order_by('-guide_like')

However, the querySet returns ["Bob", "Miller", 612072, 612072]. As you can see, Django takes the two annotate values and multiply them together and that's why I'm getting 612072.

Is there a way to call multiple annotate in a single querySet without getting these multiplied values.

EDIT: Also tried to add distinct() at the end of the query or distinct=True in each count but the call simply gets into an infinite loop.

Answer

kammala picture kammala · Jul 6, 2015

This is how django annotate produce sql code: it's do all necessary joins and then group by over all User fields, aggregating with annotation function(count in your case). So, it joins users with all their guide likes and then with all news likes and then simply counts number of rows produced per user.

If you can, you should use raw querysets, or extra Queryset method. E.g:

User.objects.all().extra(select={
    'guide_likes': 'select count(*) from tbl_guide_likes where user_id=tbl_users.id',
    'news_like': 'select count(*) from tbl_news_likes where user_id=tbl_users.id'
}).\
values_list('first_name', 'last_name', 'guide_like','news_like')

For more flexibility you can use select_params parameter of extra method for providing names of tables(which you can get through Model._meta). By the way this is very unconvenient and hackish method. Sooner or later your logic become more complicated and then you should remove it from python code to sql(stored functions/procedures) and raw queries.