Django:使用 order_by 排序数值

问题描述 投票:0回答:11

我面临的情况是,我必须通过用于存储街道地址的 CharField 输出相当大的对象列表。

我的问题是,显然数据是按 ASCII 代码排序的,因为它是 Charfield,具有可预测的结果..它像这样对数字进行排序;

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

现在显而易见的步骤是将 Charfield 更改为正确的字段类型(比如说 IntegerField),但是它无法工作,因为某些地址可能有公寓..比如“128A”。

我真的不知道如何正确订购这个..

django sorting
11个回答
28
投票

如果您确定该字段中只有整数,您可以通过

extra
方法让数据库将其转换为整数,并按以下顺序排序:

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

23
投票

Django 正在尝试弃用

extra()
方法,但在 v1.10 中引入了
Cast()
。在 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...


18
投票

如果您使用 PostgreSQL(不确定 MySQL),您可以在 char/text 字段上安全地使用以下代码并避免转换错误:

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

4
投票

我知道我迟到了,但因为它与问题密切相关,而且我很难找到这个:

您要知道,您可以直接将

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()),
        ]

来自关于订购的文档:

您还可以使用查询表达式。

来自关于数据库函数的文档:

函数也是表达式,因此它们可以与聚合函数等其他表达式一起使用和组合。


3
投票

很棒的提示!这个对我有用! :) 这是我的代码:

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

2
投票

您遇到的问题与按文件名排序时文件名的排序方式非常相似。在那里,您希望“2 Foo.mp3”出现在“12 Foo.mp3”之前。

常见的做法是将数字“规范化”,扩展为固定位数,然后根据规范化形式进行排序。也就是说,为了排序,“2 Foo.mp3”可能会扩展为“0000000002 Foo.mp3”。

Django 不会直接帮助你。您可以添加一个字段来存储“标准化”地址,并拥有数据库

order_by
,或者您可以在提交列表之前在视图中(或在视图使用的帮助程序中)对地址记录进行自定义排序记录到模板。


2
投票

在我的例子中,我有一个带有名称字段的 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')

这样,无论如何,正确的排序对我来说都是有效的。


1
投票

如果您需要对由点分隔的多个数字组成的版本号进行排序(例如

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[]                             "
        }
    )

1
投票

我正在寻找一种对

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


0
投票

该线程中的所有答案对我来说都不起作用,因为它们假设是数字文本。我找到了一个适用于部分情况的解决方案。考虑这个模型

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>]>

0
投票

我也有类似的情况。旧数据库有一个表,其中的列(名为

page
)中主要包含数字数据,类型为
varchar
。我需要按自然数字顺序对从该表查询的结果进行排序,但不需要更改结果中的数据类型。我最终将列值转换为数字仅用于排序:

(ItemPage.objects.filter(volume__id=volumeId)
.order_by('item__topic__name', F('page') * 1, 'item__name'))

我在这里看到一些其他答案表明MySQL的

CAST()
可能不适用于包含数字和字母混合的字符串,但从实验来看,我认为这不是真的。也许这是旧版本 MySQL 的问题。

无论这是否是一个问题,我决定将值乘以一。这对我来说很有效。在某个时候,我会尝试使用

CAST()
,看看它是否总是有效,以及它的执行速度是否比乘以一更快。

© www.soinside.com 2019 - 2024. All rights reserved.