Django: упорядочивание числового значения с помощью order_by

Я в ситуации, когда я должен выдать довольно большой список объектов CharField, используемый для хранения уличных адресов.

Моя проблема в том, что, очевидно, данные упорядочены по кодам ASCII, так как это Charfield с прогнозируемыми результатами.. он сортирует числа, подобные этому;

1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21....

Теперь очевидным шагом было бы изменить Charfield на собственный тип поля (IntegerField let say), однако он не может работать, поскольку у какого-либо адреса могут быть квартиры.. например, "128A".

Я действительно не знаю, как я могу заказать это правильно.

Ответ 1

Если вы уверены, что в этом поле есть только целые числа, вы можете заставить базу данных отличать ее как целое число с помощью метода extra и упорядочить это:

MyModel.objects.extra(
    select={'myinteger': 'CAST(mycharfield AS INTEGER)'}
).order_by('myinteger')

Ответ 2

Если вы используете PostgreSQL (не уверены в MySQL), вы можете безопасно использовать следующий код в char/текстовых полях и избегать ошибок при запуске:

MyModel.objects.extra(
    select={'myinteger': "CAST(substring(charfield FROM '^[0-9]+') AS INTEGER)"}
).order_by('myinteger')

Ответ 3

Django пытается отказаться от метода extra(), но ввел Cast() в версии 1.1. В sqlite (по крайней мере) CAST может принимать значение, например 10a, и отбрасывает его на целое число 10, поэтому вы можете сделать:

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

MyModel.objects.annotate(
    my_integer_field=Cast('my_char_field', IntegerField())
).order_by('my_integer_field', 'my_char_field')

который будет возвращать объекты, отсортированные по номеру улицы, сначала численно, затем в алфавитном порядке, например. ...14, 15a, 15b, 16, 16a, 17...

Ответ 4

Отличный совет! Меня устраивает!:) Что мой код:

revisioned_objects = revisioned_objects.extra(select={'casted_object_id': 'CAST(object_id AS INTEGER)'}).extra(order_by = ['casted_object_id'])

Ответ 5

Проблема, с которой вы сталкиваетесь, очень похожа на то, как упорядочиваются имена файлов при сортировке по имени файла. Там вы хотите "2 Foo.mp3" появиться перед "12 Foo.mp3".

Общим подходом является "нормализация" чисел до расширения до фиксированного числа цифр, а затем сортировка на основе нормализованной формы. То есть для целей сортировки "2 Foo.mp3" может расширяться до "0000000002 Foo.mp3".

Django не поможет вам здесь. Вы можете либо добавить поле для хранения "нормализованного" адреса, либо иметь базу данных order_by, либо вы можете сделать собственный вид в своем представлении (или в помощнике, который использует ваш просмотр) в адресных записях, прежде чем передавать список записей в шаблон.

Ответ 6

Если вам нужно отсортировать номера версий, состоящие из нескольких чисел, разделенных точкой (например, 1.9.0, 1.10.0), здесь будет только постгерское решение:

class VersionRecordManager(models.Manager):

    def get_queryset(self):
        return super().get_queryset().extra(
            select={
                'natural_version': "string_to_array(version, '.')::int[]",
            },
        )

    def available_versions(self):
        return self.filter(available=True).order_by('-natural_version')

    def last_stable(self):
        return self.available_versions().filter(stable=True).first()

class VersionRecord(models.Model):
    objects = VersionRecordManager()
    version = models.CharField(max_length=64, db_index=True)
    available = models.BooleanField(default=False, db_index=True)
    stable = models.BooleanField(default=False, db_index=True)

Если вы хотите разрешить нечисловые символы (например, 0.9.0 beta, 2.0.0 stable):

def get_queryset(self):
    return super().get_queryset().extra(
        select={
            'natural_version':
                "string_to_array(                     "  
                "   regexp_replace(                   "  # Remove everything except digits
                "       version, '[^\d\.]+', '', 'g'  "  # and dots, then split string into
                "   ), '.'                            "  # an array of integers.
                ")::int[]                             "
        }
    )

Ответ 7

Я искал способ сортировки числовых символов в CharField, и мой поиск привел меня сюда. Поля name в моих объектах являются лицензиями CC, например, "CC BY-NC 4.0".

Поскольку extra() устарела, я смог сделать это следующим образом:

MyObject.objects.all()
    .annotate(sorting_int=Cast(Func(F('name'), Value('\D'), Value(''), Value('g'), function='regexp_replace'), IntegerField()))
    .order_by('-sorting_int')

Таким образом, MyObject с name='CC BY-NC 4.0' теперь имеет sorting_int=40.