Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django queryset order_by absolute value

I have a queryset that has a description attribute and a balance attribute. Currently, when I produce the queryset it is ordered from smallest to largest - -

Model.objects.order_by('balance')

#output

Thing1          -120000
Thing2             -300
Thing3             7000
Thing4           100000

What I would like to order by is decending absolute value such that the result is:

#Desired Output

Thing1          -120000
Thing4           100000
Thing3             7000
Thing2             -300

I've tried passing various methods inside the order_by() but I think it needs to be an SQL type argument - which I can't figure out. I found this which uses a func expression when annotating. I couldn't get it to work with the order_by and the docs didn't help much.

Is there a way to order a queryset in such a way?

like image 722
Bill Armstrong Avatar asked Jun 16 '18 05:06

Bill Armstrong


2 Answers

You can annotate absolute values and order by annotated value. You can use - sign to indicate descending order:

from django.db.models import Func, F

Model.objects.annotate(abs_balance=Func(F('balance'), function='ABS')).order_by('-abs_balance')
like image 67
neverwalkaloner Avatar answered Nov 14 '22 03:11

neverwalkaloner


Answer by @neverwalkaloner is excellent, but doesn't work when dealing with datetime objects, e.g. if you want to order objects by distance to some specific timestamp.

For example:

queryset.annotate(
    distance=Func(F('datetime') - datetime_goal, function='ABS')
).order_by('distance')

fails with ProgrammingError: function abs(interval) does not exist, which is explained in this question.

In this case, you can use a Case expression to simulate absolute value, like this:

from django.db.models import Case, When
queryset.annotate(
    distance=Case(
        When(datetime__gte=datetime_goal, then=F('datetime')-datetime_goal),
        When(datetime__lt=datetime_goal, then=datetime_goal-F('datetime')),
    )
).order_by('distance')
like image 32
Vidak Avatar answered Nov 14 '22 02:11

Vidak