TypeError
TypeError

Reputation: 43

Django use LEFT JOIN instead of INNER JOIN

I have two models: Comments and CommentFlags

class Comments(models.Model):
    content_type = models.ForeignKey(ContentType,
                                     verbose_name=_('content type'),
                                     related_name="content_type_set_for_%(class)s",
                                     on_delete=models.CASCADE)
    object_pk = models.CharField(_('object ID'), db_index=True, max_length=64)
    content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
    submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
    ...
    ...


class CommentFlags(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comment_flags",
                             on_delete=models.CASCADE)
    comment = models.ForeignKey(Comment, related_name="flags", on_delete=models.CASCADE)
    flag = models.CharField(max_length=30, db_index=True)
    ...
    ...  

CommentFlags flag can have values: like, dislike etc.

Problem Statement: I want to get all Comments sorted by number of likes in DESC manner.

Raw Query for above problem statement:

SELECT 
    cmnts.*, coalesce(cmnt_flgs.num_like, 0) as num_like
FROM 
    comments cmnts
LEFT JOIN
(
    SELECT 
        comment_id, Count(comment_id) AS num_like 
    FROM 
        comment_flags 
    WHERE 
        flag='like' 
    GROUP BY comment_id 
) cmnt_flgs      
ON 
    cmnt_flgs.comment_id = cmnts.id 
ORDER BY 
    num_like DESC

I have not been able to convert the above query in Django ORM Queryset.

What I have tried so far...

>>> qs = (Comment.objects.filter(flags__flag='like').values('flags__comment_id')
                         .annotate(num_likes=Count('flags__comment_id')))

which generates different query.

>>> print(qs.query)
>>> SELECT "comment_flags"."comment_id",
       COUNT("comment_flags"."comment_id") AS "num_likes"
    FROM "comments"
    INNER JOIN "comment_flags"
    ON ("comments"."id" = "comment_flags"."comment_id")
    WHERE "comment_flags"."flag" = 'like'
    GROUP BY "comment_flags"."comment_id",
             "comments"."submit_date"
   ORDER BY "comments"."submit_date" ASC
   LIMIT 21

Problem with above ORM queryset is, it uses InnerJoin and also I don't know how it adds submit_date in groupby clause.

Can you please suggest me a way to convert above mentioned Raw query to Django ORM queryset ?

Upvotes: 0

Views: 863

Answers (1)

GwynBleidD
GwynBleidD

Reputation: 20549

You can try using filter argument in Count:

qs = (Comment.objects.all()
      .annotate(num_likes=Count('flags__comment_id', filter=Q(flags__flag='like'))))

It may produce slightly different query that you're expecting, depending on the database backend, but it should have equivalent behavior.

Upvotes: 1

Related Questions