Django: упорядочение числового значения с помощью order_by - PullRequest
13 голосов
/ 04 июня 2010

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

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

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

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

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

Ответы [ 6 ]

20 голосов
/ 04 июня 2010

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

MyModel.objects.extra(
    select={'myinteger': 'CAST(mycharfield AS INTEGER)'}
).order_by('myinteger')
12 голосов
/ 20 марта 2013

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

MyModel.objects.extra(
    select={'myinteger': "CAST(substring(charfield FROM '^[0-9]+') AS INTEGER)"}
).order_by('myinteger')
9 голосов
/ 25 сентября 2016

Django пытается отказаться от метода extra(), но ввел Cast() в v1.10. В 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...

3 голосов
/ 20 января 2011

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

revisioned_objects = revisioned_objects.extra(select={'casted_object_id': 'CAST(object_id AS INTEGER)'}).extra(order_by = ['casted_object_id'])
2 голосов
/ 04 июня 2010

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

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

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

1 голос
/ 08 августа 2017

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

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[]                             "
        }
    )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...