29

Recently started using Django ORM.I want to execute this query

 select student_id from students where student_id like "%97318%" order by CAST(student_id as UNSIGNED) desc;   

where student_id is a CharField which I want as integer for querying. I tried with

  students.objects.filter(student_id__contains "97318").order('-student_id')

works fine. But don't know and couldn't find how to cast "student_id" to int like the actual MySQL query mentioned above with "Django ORM". should I use raw query or is there a way out? Let me know your suggestions.

Learner_Programmer
  • 1,259
  • 1
  • 13
  • 38
  • You're missing `=` on the `filter(...)`. If raw query works for you, I wonder is there any special reason you *have to* use ORM? – Anzel Jan 23 '15 at 01:28
  • 1
    @Anzel You can't order strings by their number values. `"2" > "11"` but `2 < 11` – catavaran Jan 23 '15 at 01:31
  • @catavaran, I agreed, but still doesn't explain why this *has to* be in ORM, whereas raw sql can perform just fine – Anzel Jan 23 '15 at 01:33
  • @Anzel raw sql is not required here. `extra()` can do the job just fine. – catavaran Jan 23 '15 at 01:36

4 Answers4

63

An updated alternative without requiring the use of extra is the cast function (new in Django 1.10):

>>> from django.db.models import FloatField
>>> from django.db.models.functions import Cast
>>> Value.objects.create(integer=4)
>>> value = Value.objects.annotate(as_float=Cast('integer', FloatField())).get()> 
>>> print(value.as_float)
4.0

From https://docs.djangoproject.com/en/1.10/ref/models/database-functions/#cast

erikreed
  • 1,447
  • 1
  • 16
  • 21
13

Use queryset's extra() method:

students.objects.filter(student_id__contains="97318") \
                .extra({'stident_id_uint': "CAST(student_id as UNSIGNED)"}) \
                .order_by('-student_id_uint')
catavaran
  • 44,703
  • 8
  • 98
  • 85
9

I have tried extra() and annotate() to CAST, but they did not work well with related fields and generates JOINS resulting unexpected queryset sometimes.

What I ended up was to create a Custom Lookup.

The documentation is few but can be found at here and here

Here is my example:

@Field.register_lookup
class IntegerValue(Transform):
    # Register this before you filter things, for example in models.py
    lookup_name = 'int'  # Used as object.filter(LeftField__int__gte, "777")
    bilateral = True  # To cast both left and right

    def as_sql(self, compiler, connection):
        sql, params = compiler.compile(self.lhs)
        sql = 'CAST(%s AS UNSIGNED)' % sql
        return sql, params

Then below should work:

students.objects.filter(student_id__int__gte="97318").order('-student_id')
KinoP
  • 1,532
  • 15
  • 23
2

Use cast from Django. If you are working with Django CBV (class based view) you can do this way:

models.py:

class Document(TenantAwareModel):
    document_data = JSONField(null=True)

views.py:

from django.db.models import TextField
from django.db.models.functions import Cast

def get_queryset(self):
    document = Document.objects.annotate(document_data_as_text=Cast('document_data', TextField()))
    user = self.request.user
    tenant = self.request.user.tenant
    return document.filter(tenant=tenant)

Then, in your method you can use document_data_as_text as a normal field to filter by it:

def list(self, request, *args, **kwargs):
    document_part_filter_param = request.query_params.get("documentPart")
    if document_part_filter_param:
        queryset = queryset.filter(document_data_as_text__unaccent__icontains=document_part_filter_param)
Iasmini Gomes
  • 727
  • 1
  • 9
  • 14