Django: упорядочение числового значения с помощью order_by
Я нахожусь в ситуации, когда я должен вывести довольно большой список объектов с помощью CharField, используемого для хранения уличных адресов.
Моя проблема в том, что данные, очевидно, упорядочены по ASCII-кодам, так как это поле Чарфилда с предсказуемыми результатами.
1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21....
Теперь очевидным шагом было бы изменить Charfield на правильный тип поля (скажем, IntegerField), однако он не может работать, так как на некотором адресе могут быть квартиры... как "128A".
Я действительно не знаю, как я могу заказать это правильно..
11 ответов
Если вы уверены, что в поле есть только целые числа, вы можете заставить базу данных преобразовать ее как целое число через extra
метод, и порядок этим:
MyModel.objects.extra(
select={'myinteger': 'CAST(mycharfield AS INTEGER)'}
).order_by('myinteger')
Джанго пытается осудить 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...
Если вы используете PostgreSQL (не уверен в MySQL), вы можете безопасно использовать следующий код в полях char/text и избежать ошибок приведения:
MyModel.objects.extra(
select={'myinteger': "CAST(substring(charfield FROM '^[0-9]+') AS INTEGER)"}
).order_by('myinteger')
Теперь, когда я опаздываю с этим, но поскольку это сильно связано с вопросом, и мне было трудно найти это:
Вы должны знать, что можете напрямую поставить
Cast
в
ordering
вариант вашей модели.
from django.db import models
from django.db.models.functions import Cast
class Address(models.Model):
street_number = models.CharField()
class Meta:
ordering = [
Cast("street_number"), output_field=models.IntegerField(),
]
Из документа о заказе :
Вы также можете использовать выражения запроса.
И из документа о функциях базы данных :
Функции также являются выражениями, поэтому их можно использовать и комбинировать с другими выражениями, такими как агрегатные функции.
Отличный совет! Меня устраивает!:) Это мой код:
revisioned_objects = revisioned_objects.extra(select={'casted_object_id': 'CAST(object_id AS INTEGER)'}).extra(order_by = ['casted_object_id'])
В случае, если вам нужно отсортировать номера версий, состоящие из нескольких чисел, разделенных точкой (например, 1.9.0, 1.10.0
), вот решение только для postgres:
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[] "
}
)
В моем случае у меня есть CharField с полем имени, которое, например, имеет смешанные значения (int+string). "a1", "f65", "P", "55" и т. д.
Решил проблему, используя приведение sql (проверено с postgres и mysql), сначала я пытаюсь отсортировать по приведенному целочисленному значению, а затем по исходному значению поля имени.
parking_slots = ParkingSlot.objects.all().extra(
select={'num_from_name': 'CAST(name AS INTEGER)'}
).order_by('num_from_name', 'name')
Так в любом случае у меня работает правильная сортировка.
Проблема, с которой вы столкнулись, очень похожа на порядок имен файлов при сортировке по имени файла. Там вы хотите, чтобы "2 Foo.mp3" появлялся перед "12 Foo.mp3".
Обычный подход состоит в том, чтобы "нормализовать" числа к расширению до фиксированного числа цифр, а затем сортировать на основе нормализованной формы. То есть для целей сортировки "2 Foo.mp3" может расширяться до "0000000002 Foo.mp3".
Джанго не поможет вам здесь напрямую. Вы можете добавить поле для хранения "нормализованного" адреса и иметь базу данных order_by
это, или вы можете выполнить пользовательскую сортировку в своем представлении (или в помощнике, который использует ваше представление) в адресных записях перед передачей списка записей в шаблон.
Я искал способ отсортировать числовые символы в 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
.
У меня похожая ситуация. В старой базе данных есть таблица с преимущественно числовыми данными в столбце (с именем
(ItemPage.objects.filter(volume__id=volumeId)
.order_by('item__topic__name', F('page') * 1, 'item__name'))
Я вижу, что некоторые другие ответы здесь указывают на то, что MySQL может не работать со строками, содержащими смесь цифр и букв, но, судя по экспериментам, я не думаю, что это правда. Возможно, это проблема старых версий MySQL.
Проблема это или нет, но вместо этого я решил умножить значение на единицу. Это хорошо работает для меня. Когда-нибудь я поэкспериментирую
Все ответы в этой теме не сработали для меня, потому что они предполагают числовой текст. Я нашел решение, которое будет работать для подмножества случаев. Рассмотрим эту модель
Class Block(models.Model):
title = models.CharField()
скажем, у меня есть поля, в которых иногда есть начальные символы и конечные числовые символы. Если я попытаюсь упорядочить нормально
>>> Block.objects.all().order_by('title')
<QuerySet [<Block: 1>, <Block: 10>, <Block: 15>, <Block: 2>, <Block: N1>, <Block: N12>, <Block: N4>]>
Как и ожидалось, это правильно в алфавитном порядке, но не имеет смысла для нас, людей. Трюк, который я сделал для этого конкретного варианта использования, заключается в том, чтобы заменить любой текст, который я нашел, числом 9999, а затем привести значение к целому числу и упорядочить его.
для большинства случаев с ведущими символами это даст желаемый результат. Смотри ниже
from django.db.models.expressions import RawSQL
>>> Block.objects.all()\
.annotate(my_faux_integer=RawSQL("CAST(regexp_replace(title, '[A-Z]+', '9999', 'g') AS INTEGER)", ''))\
.order_by('my_faux_integer', 'title')
<QuerySet [<Block: 1>, <Block: 2>, <Block: 10>, <Block: 15>, <Block: N1>, <Block: N4>, <Block: N12>]>