Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ORM: create ranking based on related model count

I have 2 models in my database: Movie and Comment (it has a foreign key pointing to Movie).

from django.db import models


class Movie(models.Model):
    title = models.CharField(max_length=120, blank=False, unique=True)

class Comment(models.Model):
    movie = models.ForeignKey(
        movie_models.Movie, null=False, on_delete=models.CASCADE
    )
    body = models.TextField(blank=True)

What I am trying to do is create a ranking of Movies where the position is determined by the number of related Comments.

I can annotate number of comments and order the result properly:

from django.db.models import Count

qs = Movie.objects.annotate(
    total_comments=Count('comment')
).order_by('-total_comments')

This gives me queryset in correct order but I would like to do one more thing - annotate the 'rank' to each row.

So my question is: how can I annotate 'rank' to each row of the result? Note that it is required for movies with the same amount of comments to have the same rank.

|  movie_title | total_comments | rank |
|--------------|----------------|------|
| mov1         | 10             | 1    |
| mov2         | 5              | 2    |
| mov3         | 5              | 2    |
| mov4         | 3              | 3    |

I tried to use window functions, as some of the examples seemed legit for me:

from django.db.models import F, Window

dense_rank = Window(
    expression=DenseRank(),
    order_by=F('total_comments').desc(),
)

qs = Movie.objects.annotate(
    total_comments=Count('comment')
).annotate(rank=dense_rank)

But running this query raises django.db.utils.OperationalError: near "(": syntax error

like image 339
umat Avatar asked Oct 20 '25 16:10

umat


1 Answers

RowNumber should be sufficient if the query is appropriately ordered:

from django.db.models import Count
from django.db.models.expressions import F, Window
from django.db.models.functions.window import RowNumber

qs = (Movie.objects
    .annotate(total_comments=Count('comment'))
    .order_by('total_comments')
    .annotate(rank = Window(expression=RowNumber())
)

But since the row number is inherent in the sequence of the records, Kevin is right in suggesting an easy Python method (e.g. enumerate) as you're going to be iterating over the recordset at some point anyway.

It would be different if you wanted a different ordering for your recordset than for the ranking, then it would make sense to use Window with a separate order_by parameter.

I'm not sure if RowNumber is supported by all backends.

like image 191
Endre Both Avatar answered Oct 23 '25 07:10

Endre Both



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!