Ordering Django querysets using a JSONField's

2019-08-28 21:18发布

问题:

I have a model that kinda looks like this:

class Person(models.Model):
    data = JSONField()

The data field has 2 properties, name, and age. Now, lets say I want to get a paginated queryset (each page containing 20 people), with a filter where age is greater than 25, and the queryset is to be ordered in descending order. In a usual setup, that is, a normalized database, I can write this query like so:

person_list_page_1 = Person.objects.filter(age > 25).order_by('-age')[:20]

Now, what is the equivalence of the above when filtering and ordering using keys stored in the JSONField? I have researched into this, and it seems it was meant to be a feature for 2.1, but I can't seem to find anything relevant.

Link to the ticket about it being implemented in the future

I also have another question. Lets say we filter and order using the JSONField. Will the ORM have to get all the objects, filter, and order them before sending the first 20 in such a case? That is, will performance be legitimately slower?

Obviously, I know a normalized database is far better for these things, but my hands are kinda tied.

回答1:

You can use the postgresql sql syntax to extract subfields. Then they can be used just as any other field on the model in queryset filters.

from django.db.models.expressions import RawSQL
Person.objects.annotate(
    age=RawSQL("(data->>'age')::int", [])
).filter(age__gte=25).order_by('-age')[:20]

See the postgresql docs for other operators and functions. In some cases, you might have to add explicit typecasts (::int, for example)

https://www.postgresql.org/docs/current/static/functions-json.html

Performance will be slower than with a proper field, but it's not bad.